Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into import_zip

This commit is contained in:
Trial97 2024-06-04 11:04:02 +03:00
commit 45463dcd75
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
39 changed files with 296 additions and 141 deletions

View File

@ -25,7 +25,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v2.5.0 uses: korthout/backport-action@v3.0.2
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -611,7 +611,7 @@ jobs:
flatpak: flatpak:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08 image: bilelmoussaoui/flatpak-github-actions:kde-6.7
options: --privileged options: --privileged
steps: steps:
- name: Checkout - name: Checkout

View File

@ -17,7 +17,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26 - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
- uses: DeterminateSystems/update-flake-lock@v21 - uses: DeterminateSystems/update-flake-lock@v21
with: with:

52
flake.lock generated
View File

@ -23,11 +23,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1714641030, "lastModified": 1717285511,
"narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=", "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e", "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -36,24 +36,6 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": { "gitignore": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -93,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1715413075, "lastModified": 1717112898,
"narHash": "sha256-FCi3R1MeS5bVp0M0xTheveP6hhcCYfW/aghSTPebYL4=", "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e4e7a43a9db7e22613accfeb1005cca1b2b1ee0d", "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -112,7 +94,6 @@
"flake-compat": [ "flake-compat": [
"flake-compat" "flake-compat"
], ],
"flake-utils": "flake-utils",
"gitignore": "gitignore", "gitignore": "gitignore",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
@ -122,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1714478972, "lastModified": 1716213921,
"narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=", "narHash": "sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "2849da033884f54822af194400f8dff435ada242", "rev": "0e8fcc54b842ad8428c9e705cb5994eaf05c26a0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -143,21 +124,6 @@
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": "pre-commit-hooks"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@ -1,6 +1,6 @@
id: org.prismlauncher.PrismLauncher id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform runtime: org.kde.Platform
runtime-version: 5.15-23.08 runtime-version: 6.7
sdk: org.kde.Sdk sdk: org.kde.Sdk
sdk-extensions: sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk21 - org.freedesktop.Sdk.Extension.openjdk21
@ -38,7 +38,6 @@ modules:
config-opts: config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak - -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DLauncher_QT_VERSION_MAJOR=5
build-options: build-options:
env: env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17

View File

@ -48,6 +48,7 @@
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
#include "settings/INIFile.h" #include "settings/INIFile.h"
#include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
@ -874,6 +875,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// FIXME: what to do with these? // FIXME: what to do with these?
m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory())); m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));
m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory())); m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
m_profilers.insert("generic", std::shared_ptr<BaseProfilerFactory>(new GenericProfilerFactory()));
for (auto profiler : m_profilers.values()) { for (auto profiler : m_profilers.values()) {
profiler->registerSettings(m_settings); profiler->registerSettings(m_settings);
} }

View File

@ -443,6 +443,8 @@ set(TOOLS_SOURCES
tools/JVisualVM.h tools/JVisualVM.h
tools/MCEditTool.cpp tools/MCEditTool.cpp
tools/MCEditTool.h tools/MCEditTool.h
tools/GenericProfiler.cpp
tools/GenericProfiler.h
) )
set(META_SOURCES set(META_SOURCES

View File

@ -378,6 +378,7 @@ enum class FilesystemType {
HFSX, HFSX,
FUSEBLK, FUSEBLK,
F2FS, F2FS,
BCACHEFS,
UNKNOWN UNKNOWN
}; };
@ -406,6 +407,7 @@ static const QMap<FilesystemType, QStringList> s_filesystem_type_names = { { Fil
{ FilesystemType::HFSX, { "HFSX" } }, { FilesystemType::HFSX, { "HFSX" } },
{ FilesystemType::FUSEBLK, { "FUSEBLK" } }, { FilesystemType::FUSEBLK, { "FUSEBLK" } },
{ FilesystemType::F2FS, { "F2FS" } }, { FilesystemType::F2FS, { "F2FS" } },
{ FilesystemType::BCACHEFS, { "BCACHEFS" } },
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } }; { FilesystemType::UNKNOWN, { "UNKNOWN" } } };
/** /**
@ -458,7 +460,7 @@ QString nearestExistentAncestor(const QString& path);
FilesystemInfo statFS(const QString& path); FilesystemInfo statFS(const QString& path);
static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
FilesystemType::XFS, FilesystemType::REFS }; FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS };
/** /**
* @brief if the Filesystem is reflink/clone capable * @brief if the Filesystem is reflink/clone capable

View File

@ -212,3 +212,25 @@ QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegular
right = s.mid(end); right = s.mid(end);
return qMakePair(left, right); return qMakePair(left, right);
} }
static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>");
QString StringUtils::htmlListPatch(QString htmlStr)
{
int pos = htmlStr.indexOf(ulMatcher);
int imgPos;
while (pos != -1) {
pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the </ul> tag. Add one for zeroeth index
imgPos = htmlStr.indexOf("<img ", pos);
if (imgPos == -1)
break; // no image after the tag
auto textBetween = htmlStr.mid(pos, imgPos - pos).trimmed(); // trim all white spaces
if (textBetween.isEmpty())
htmlStr.insert(pos, "<br>");
pos = htmlStr.indexOf(ulMatcher, pos);
}
return htmlStr;
}

View File

@ -85,4 +85,6 @@ QPair<QString, QString> splitFirst(const QString& s, const QString& sep, Qt::Cas
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive); QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re); QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
QString htmlListPatch(QString htmlStr);
} // namespace StringUtils } // namespace StringUtils

View File

@ -362,6 +362,12 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
} }
auto home = qEnvironmentVariable("HOME");
// javas downloaded by sdkman
javas.append(FS::PathCombine(home, ".sdkman/candidates/java"));
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();

View File

@ -1000,7 +1000,7 @@ QString MinecraftInstance::getStatusbarDescription()
QString description; QString description;
description.append(tr("Minecraft %1").arg(mcVersion)); description.append(tr("Minecraft %1").arg(mcVersion));
if (m_settings->get("ShowGameTime").toBool()) { if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) { if (lastTimePlayed() > 0 && lastLaunch() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append( description.append(
tr(", last played on %1 for %2") tr(", last played on %1 for %2")

View File

@ -42,17 +42,28 @@ QString toHTML(QList<Mod*> mods, OptionalData extraData)
} }
if (extraData & Authors && !mod->authors().isEmpty()) if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", ").toHtmlEscaped(); line += " by " + mod->authors().join(", ").toHtmlEscaped();
if (extraData & FileName)
line += QString(" (%1)").arg(mod->fileinfo().fileName().toHtmlEscaped());
lines.append(QString("<li>%1</li>").arg(line)); lines.append(QString("<li>%1</li>").arg(line));
} }
return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t")); return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t"));
} }
QString toMarkdownEscaped(QString src)
{
for (auto ch : "\\`*_{}[]<>()#+-.!|")
src.replace(ch, QString("\\%1").arg(ch));
return src;
}
QString toMarkdown(QList<Mod*> mods, OptionalData extraData) QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
{ {
QStringList lines; QStringList lines;
for (auto mod : mods) { for (auto mod : mods) {
auto meta = mod->metadata(); auto meta = mod->metadata();
auto modName = mod->name(); auto modName = toMarkdownEscaped(mod->name());
if (extraData & Url) { if (extraData & Url) {
auto url = mod->metaurl(); auto url = mod->metaurl();
if (!url.isEmpty()) if (!url.isEmpty())
@ -60,14 +71,16 @@ QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
} }
auto line = modName; auto line = modName;
if (extraData & Version) { if (extraData & Version) {
auto ver = mod->version(); auto ver = toMarkdownEscaped(mod->version());
if (ver.isEmpty() && meta != nullptr) if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString(); ver = toMarkdownEscaped(meta->version().toString());
if (!ver.isEmpty()) if (!ver.isEmpty())
line += QString(" [%1]").arg(ver); line += QString(" [%1]").arg(ver);
} }
if (extraData & Authors && !mod->authors().isEmpty()) if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", "); line += " by " + toMarkdownEscaped(mod->authors().join(", "));
if (extraData & FileName)
line += QString(" (%1)").arg(toMarkdownEscaped(mod->fileinfo().fileName()));
lines << "- " + line; lines << "- " + line;
} }
return lines.join("\n"); return lines.join("\n");
@ -95,6 +108,8 @@ QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
} }
if (extraData & Authors && !mod->authors().isEmpty()) if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", "); line += " by " + mod->authors().join(", ");
if (extraData & FileName)
line += QString(" (%1)").arg(mod->fileinfo().fileName());
lines << line; lines << line;
} }
return lines.join("\n"); return lines.join("\n");
@ -122,6 +137,8 @@ QString toJSON(QList<Mod*> mods, OptionalData extraData)
} }
if (extraData & Authors && !mod->authors().isEmpty()) if (extraData & Authors && !mod->authors().isEmpty())
line["authors"] = QJsonArray::fromStringList(mod->authors()); line["authors"] = QJsonArray::fromStringList(mod->authors());
if (extraData & FileName)
line["filename"] = mod->fileinfo().fileName();
lines << line; lines << line;
} }
QJsonDocument doc; QJsonDocument doc;
@ -154,6 +171,8 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
authors = QString("\"%1\"").arg(mod->authors().join(",")); authors = QString("\"%1\"").arg(mod->authors().join(","));
data << authors; data << authors;
} }
if (extraData & FileName)
data << mod->fileinfo().fileName();
lines << data.join(","); lines << data.join(",");
} }
return lines.join("\n"); return lines.join("\n");
@ -189,11 +208,13 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
if (ver.isEmpty() && meta != nullptr) if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString(); ver = meta->version().toString();
auto authors = mod->authors().join(", "); auto authors = mod->authors().join(", ");
auto filename = mod->fileinfo().fileName();
lines << QString(lineTemplate) lines << QString(lineTemplate)
.replace("{name}", modName) .replace("{name}", modName)
.replace("{url}", url) .replace("{url}", url)
.replace("{version}", ver) .replace("{version}", ver)
.replace("{authors}", authors); .replace("{authors}", authors)
.replace("{filename}", filename);
} }
return lines.join("\n"); return lines.join("\n");
} }

View File

@ -23,11 +23,7 @@
namespace ExportToModList { namespace ExportToModList {
enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM };
enum OptionalData { enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 };
Authors = 1 << 0,
Url = 1 << 1,
Version = 1 << 2,
};
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData); QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
QString exportToModList(QList<Mod*> mods, QString lineTemplate); QString exportToModList(QList<Mod*> mods, QString lineTemplate);
} // namespace ExportToModList } // namespace ExportToModList

View File

@ -40,6 +40,7 @@
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
#include "ui/dialogs/CustomMessageBox.h"
#endif #endif
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : ConcurrentTask(nullptr, job_name), m_network(network) NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : ConcurrentTask(nullptr, job_name), m_network(network)
@ -63,8 +64,11 @@ void NetJob::executeNextSubTask()
// We're finished, check for failures and retry if we can (up to 3 times) // We're finished, check for failures and retry if we can (up to 3 times)
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) { if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1; m_try += 1;
while (!m_failed.isEmpty()) while (!m_failed.isEmpty()) {
m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); auto task = m_failed.take(*m_failed.keyBegin());
m_done.remove(task.get());
m_queue.enqueue(task);
}
} }
ConcurrentTask::executeNextSubTask(); ConcurrentTask::executeNextSubTask();
} }
@ -136,3 +140,25 @@ void NetJob::updateState()
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize())));
} }
void NetJob::emitFailed(QString reason)
{
#if defined(LAUNCHER_APPLICATION)
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
"The tasks failed\n"
"Failed urls\n" +
getFailedFiles().join("\n\t") +
"\n"
"If this continues to happen please check the logs of the application"
"Do you want to retry?",
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response == QMessageBox::Yes) {
m_try = 0;
executeNextSubTask();
return;
}
#endif
ConcurrentTask::emitFailed(reason);
}

View File

@ -66,6 +66,7 @@ class NetJob : public ConcurrentTask {
public slots: public slots:
// Qt can't handle auto at the start for some reason? // Qt can't handle auto at the start for some reason?
bool abort() override; bool abort() override;
void emitFailed(QString reason) override;
protected slots: protected slots:
void executeNextSubTask() override; void executeNextSubTask() override;

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GenericProfiler.h"
#include "BaseInstance.h"
#include "launch/LaunchTask.h"
#include "settings/SettingsObject.h"
class GenericProfiler : public BaseProfiler {
Q_OBJECT
public:
GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0);
protected:
void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process);
};
GenericProfiler::GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent)
: BaseProfiler(settings, instance, parent)
{}
void GenericProfiler::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process)
{
emit readyToLaunch(tr("Started process: %1").arg(process->pid()));
}
BaseExternalTool* GenericProfilerFactory::createTool(InstancePtr instance, QObject* parent)
{
return new GenericProfiler(globalSettings, instance, parent);
}
#include "GenericProfiler.moc"

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "BaseProfiler.h"
class GenericProfilerFactory : public BaseProfilerFactory {
public:
QString name() const override { return "Generic"; }
void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override{};
BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override;
bool check([[maybe_unused]] QString* error) override { return true; };
bool check([[maybe_unused]] const QString& path, [[maybe_unused]] QString* error) override { return true; };
};

View File

@ -96,7 +96,6 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h" #include "ui/dialogs/ExportPackDialog.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h" #include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
@ -209,7 +208,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
exportInstanceMenu->addAction(ui->actionExportInstanceZip); exportInstanceMenu->addAction(ui->actionExportInstanceZip);
exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack);
exportInstanceMenu->addAction(ui->actionExportInstanceToModList);
ui->actionExportInstance->setMenu(exportInstanceMenu); ui->actionExportInstance->setMenu(exportInstanceMenu);
} }
@ -1416,14 +1414,6 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
} }
} }
void MainWindow::on_actionExportInstanceToModList_triggered()
{
if (m_selectedInstance) {
ExportToModListDialog dlg(m_selectedInstance, this);
dlg.exec();
}
}
void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionExportInstanceFlamePack_triggered()
{ {
if (m_selectedInstance) { if (m_selectedInstance) {

View File

@ -158,7 +158,6 @@ class MainWindow : public QMainWindow {
void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered(); void on_actionExportInstanceFlamePack_triggered();
void on_actionExportInstanceToModList_triggered();
void on_actionRenameInstance_triggered(); void on_actionRenameInstance_triggered();

View File

@ -491,15 +491,6 @@
<string>CurseForge (zip)</string> <string>CurseForge (zip)</string>
</property> </property>
</action> </action>
<action name="actionExportInstanceToModList">
<property name="icon">
<iconset theme="new">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Mod List</string>
</property>
</action>
<action name="actionCreateInstanceShortcut"> <action name="actionCreateInstanceShortcut">
<property name="icon"> <property name="icon">
<iconset theme="shortcut"> <iconset theme="shortcut">

View File

@ -38,6 +38,7 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
#include "ui_AboutDialog.h" #include "ui_AboutDialog.h"
#include <net/NetJob.h> #include <net/NetJob.h>
@ -139,10 +140,10 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
setWindowTitle(tr("About %1").arg(launcherName)); setWindowTitle(tr("About %1").arg(launcherName));
QString chtml = getCreditsHtml(); QString chtml = getCreditsHtml();
ui->creditsText->setHtml(chtml); ui->creditsText->setHtml(StringUtils::htmlListPatch(chtml));
QString lhtml = getLicenseHtml(); QString lhtml = getLicenseHtml();
ui->licenseText->setHtml(lhtml); ui->licenseText->setHtml(StringUtils::htmlListPatch(lhtml));
ui->urlLabel->setOpenExternalLinks(true); ui->urlLabel->setOpenExternalLinks(true);

View File

@ -22,8 +22,7 @@
#include <QTextEdit> #include <QTextEdit>
#include "FileSystem.h" #include "FileSystem.h"
#include "Markdown.h" #include "Markdown.h"
#include "minecraft/MinecraftInstance.h" #include "StringUtils.h"
#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/helpers/ExportToModList.h" #include "modplatform/helpers/ExportToModList.h"
#include "ui_ExportToModListDialog.h" #include "ui_ExportToModListDialog.h"
@ -41,38 +40,31 @@ const QHash<ExportToModList::Formats, QString> ExportToModListDialog::exampleLin
{ ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" }, { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" },
}; };
ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent) ExportToModListDialog::ExportToModListDialog(QString name, QList<Mod*> mods, QWidget* parent)
: QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog) : QDialog(parent), m_mods(mods), m_template_changed(false), m_name(name), ui(new Ui::ExportToModListDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
enableCustom(false); enableCustom(false);
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
if (mcInstance) {
mcInstance->loaderModList()->update();
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
m_allMods = mcInstance->loaderModList()->allMods();
triggerImp();
});
}
connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged); connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged);
connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
connect(ui->filenameCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); }); connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); });
connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); });
connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); });
connect(ui->filenameButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::FileName); });
connect(ui->templateText, &QTextEdit::textChanged, this, [this] { connect(ui->templateText, &QTextEdit::textChanged, this, [this] {
if (ui->templateText->toPlainText() != exampleLines[format]) if (ui->templateText->toPlainText() != exampleLines[m_format])
ui->formatComboBox->setCurrentIndex(5); ui->formatComboBox->setCurrentIndex(5);
else triggerImp();
triggerImp();
}); });
connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) { connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) {
this->ui->finalText->selectAll(); this->ui->finalText->selectAll();
this->ui->finalText->copy(); this->ui->finalText->copy();
}); });
triggerImp();
} }
ExportToModListDialog::~ExportToModListDialog() ExportToModListDialog::~ExportToModListDialog()
@ -86,38 +78,38 @@ void ExportToModListDialog::formatChanged(int index)
case 0: { case 0: {
enableCustom(false); enableCustom(false);
ui->resultText->show(); ui->resultText->show();
format = ExportToModList::HTML; m_format = ExportToModList::HTML;
break; break;
} }
case 1: { case 1: {
enableCustom(false); enableCustom(false);
ui->resultText->show(); ui->resultText->show();
format = ExportToModList::MARKDOWN; m_format = ExportToModList::MARKDOWN;
break; break;
} }
case 2: { case 2: {
enableCustom(false); enableCustom(false);
ui->resultText->hide(); ui->resultText->hide();
format = ExportToModList::PLAINTXT; m_format = ExportToModList::PLAINTXT;
break; break;
} }
case 3: { case 3: {
enableCustom(false); enableCustom(false);
ui->resultText->hide(); ui->resultText->hide();
format = ExportToModList::JSON; m_format = ExportToModList::JSON;
break; break;
} }
case 4: { case 4: {
enableCustom(false); enableCustom(false);
ui->resultText->hide(); ui->resultText->hide();
format = ExportToModList::CSV; m_format = ExportToModList::CSV;
break; break;
} }
case 5: { case 5: {
m_template_changed = true; m_template_changed = true;
enableCustom(true); enableCustom(true);
ui->resultText->hide(); ui->resultText->hide();
format = ExportToModList::CUSTOM; m_format = ExportToModList::CUSTOM;
break; break;
} }
} }
@ -126,8 +118,8 @@ void ExportToModListDialog::formatChanged(int index)
void ExportToModListDialog::triggerImp() void ExportToModListDialog::triggerImp()
{ {
if (format == ExportToModList::CUSTOM) { if (m_format == ExportToModList::CUSTOM) {
ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText())); ui->finalText->setPlainText(ExportToModList::exportToModList(m_mods, ui->templateText->toPlainText()));
return; return;
} }
auto opt = 0; auto opt = 0;
@ -137,16 +129,18 @@ void ExportToModListDialog::triggerImp()
opt |= ExportToModList::Version; opt |= ExportToModList::Version;
if (ui->urlCheckBox->isChecked()) if (ui->urlCheckBox->isChecked())
opt |= ExportToModList::Url; opt |= ExportToModList::Url;
auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt)); if (ui->filenameCheckBox->isChecked())
opt |= ExportToModList::FileName;
auto txt = ExportToModList::exportToModList(m_mods, m_format, static_cast<ExportToModList::OptionalData>(opt));
ui->finalText->setPlainText(txt); ui->finalText->setPlainText(txt);
switch (format) { switch (m_format) {
case ExportToModList::CUSTOM: case ExportToModList::CUSTOM:
return; return;
case ExportToModList::HTML: case ExportToModList::HTML:
ui->resultText->setHtml(txt); ui->resultText->setHtml(StringUtils::htmlListPatch(txt));
break; break;
case ExportToModList::MARKDOWN: case ExportToModList::MARKDOWN:
ui->resultText->setHtml(markdownToHTML(txt)); ui->resultText->setHtml(StringUtils::htmlListPatch(markdownToHTML(txt)));
break; break;
case ExportToModList::PLAINTXT: case ExportToModList::PLAINTXT:
break; break;
@ -155,7 +149,7 @@ void ExportToModListDialog::triggerImp()
case ExportToModList::CSV: case ExportToModList::CSV:
break; break;
} }
auto exampleLine = exampleLines[format]; auto exampleLine = exampleLines[m_format];
if (!m_template_changed && ui->templateText->toPlainText() != exampleLine) if (!m_template_changed && ui->templateText->toPlainText() != exampleLine)
ui->templateText->setPlainText(exampleLine); ui->templateText->setPlainText(exampleLine);
} }
@ -163,9 +157,9 @@ void ExportToModListDialog::triggerImp()
void ExportToModListDialog::done(int result) void ExportToModListDialog::done(int result)
{ {
if (result == Accepted) { if (result == Accepted) {
const QString filename = FS::RemoveInvalidFilenameChars(name); const QString filename = FS::RemoveInvalidFilenameChars(m_name);
const QString output = const QString output =
QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()), QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_name), FS::PathCombine(QDir::homePath(), filename + extension()),
"File (*.txt *.html *.md *.json *.csv)", nullptr); "File (*.txt *.html *.md *.json *.csv)", nullptr);
if (output.isEmpty()) if (output.isEmpty())
@ -178,7 +172,7 @@ void ExportToModListDialog::done(int result)
QString ExportToModListDialog::extension() QString ExportToModListDialog::extension()
{ {
switch (format) { switch (m_format) {
case ExportToModList::HTML: case ExportToModList::HTML:
return ".html"; return ".html";
case ExportToModList::MARKDOWN: case ExportToModList::MARKDOWN:
@ -197,7 +191,7 @@ QString ExportToModListDialog::extension()
void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
{ {
if (format != ExportToModList::CUSTOM) if (m_format != ExportToModList::CUSTOM)
return; return;
switch (option) { switch (option) {
case ExportToModList::Authors: case ExportToModList::Authors:
@ -209,6 +203,9 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
case ExportToModList::Version: case ExportToModList::Version:
ui->templateText->insertPlainText("{version}"); ui->templateText->insertPlainText("{version}");
break; break;
case ExportToModList::FileName:
ui->templateText->insertPlainText("{filename}");
break;
} }
} }
void ExportToModListDialog::enableCustom(bool enabled) void ExportToModListDialog::enableCustom(bool enabled)
@ -221,4 +218,7 @@ void ExportToModListDialog::enableCustom(bool enabled)
ui->urlCheckBox->setHidden(enabled); ui->urlCheckBox->setHidden(enabled);
ui->urlButton->setHidden(!enabled); ui->urlButton->setHidden(!enabled);
ui->filenameCheckBox->setHidden(enabled);
ui->filenameButton->setHidden(!enabled);
} }

View File

@ -20,7 +20,6 @@
#include <QDialog> #include <QDialog>
#include <QList> #include <QList>
#include "BaseInstance.h"
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "modplatform/helpers/ExportToModList.h" #include "modplatform/helpers/ExportToModList.h"
@ -32,7 +31,7 @@ class ExportToModListDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr); explicit ExportToModListDialog(QString name, QList<Mod*> mods, QWidget* parent = nullptr);
~ExportToModListDialog(); ~ExportToModListDialog();
void done(int result) override; void done(int result) override;
@ -46,10 +45,11 @@ class ExportToModListDialog : public QDialog {
private: private:
QString extension(); QString extension();
void enableCustom(bool enabled); void enableCustom(bool enabled);
QList<Mod*> m_allMods;
QList<Mod*> m_mods;
bool m_template_changed; bool m_template_changed;
QString name; QString m_name;
ExportToModList::Formats format = ExportToModList::Formats::HTML; ExportToModList::Formats m_format = ExportToModList::Formats::HTML;
Ui::ExportToModListDialog* ui; Ui::ExportToModListDialog* ui;
static const QHash<ExportToModList::Formats, QString> exampleLines; static const QHash<ExportToModList::Formats, QString> exampleLines;
}; };

View File

@ -117,6 +117,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="filenameCheckBox">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="versionButton"> <widget class="QPushButton" name="versionButton">
<property name="text"> <property name="text">
@ -138,6 +145,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="filenameButton">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -3,6 +3,7 @@
#include "CustomMessageBox.h" #include "CustomMessageBox.h"
#include "ProgressDialog.h" #include "ProgressDialog.h"
#include "ScrollMessageBox.h" #include "ScrollMessageBox.h"
#include "StringUtils.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
@ -473,7 +474,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
break; break;
} }
changelog_area->setHtml(text); changelog_area->setHtml(StringUtils::htmlListPatch(text));
changelog_area->setOpenExternalLinks(true); changelog_area->setOpenExternalLinks(true);
changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);

View File

@ -25,6 +25,7 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
#include "ui_UpdateAvailableDialog.h" #include "ui_UpdateAvailableDialog.h"
UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
@ -43,7 +44,7 @@ UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64)); ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64));
auto releaseNotesHtml = markdownToHTML(releaseNotes); auto releaseNotesHtml = markdownToHTML(releaseNotes);
ui->releaseNotes->setHtml(releaseNotesHtml); ui->releaseNotes->setHtml(StringUtils::htmlListPatch(releaseNotesHtml));
ui->releaseNotes->setOpenExternalLinks(true); ui->releaseNotes->setOpenExternalLinks(true);
connect(ui->skipButton, &QPushButton::clicked, this, [this]() { connect(ui->skipButton, &QPushButton::clicked, this, [this]() {

View File

@ -70,15 +70,15 @@
</layout> </layout>
</widget> </widget>
<widget class="WideBar" name="actionsToolbar"> <widget class="WideBar" name="actionsToolbar">
<property name="useDefaultAction" stdset="0">
<bool>true</bool>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Actions</string> <string>Actions</string>
</property> </property>
<property name="toolButtonStyle"> <property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum> <enum>Qt::ToolButtonTextOnly</enum>
</property> </property>
<property name="useDefaultAction" stdset="0">
<bool>true</bool>
</property>
<attribute name="toolBarArea"> <attribute name="toolBarArea">
<enum>RightToolBarArea</enum> <enum>RightToolBarArea</enum>
</attribute> </attribute>
@ -171,6 +171,17 @@
<string>Try to check or update all selected resources (all resources if none are selected)</string> <string>Try to check or update all selected resources (all resources if none are selected)</string>
</property> </property>
</action> </action>
<action name="actionExportMetadata">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Export modlist</string>
</property>
<property name="toolTip">
<string>Export mod's metadata to text</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -20,6 +20,7 @@
#include "InstanceTask.h" #include "InstanceTask.h"
#include "Json.h" #include "Json.h"
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/modrinth/ModrinthPackManifest.h"
@ -332,7 +333,7 @@ void ModrinthManagedPackPage::suggestVersion()
} }
auto version = m_pack.versions.at(index); auto version = m_pack.versions.at(index);
ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8())); ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(markdownToHTML(version.changelog.toUtf8())));
ManagedPackPage::suggestVersion(); ManagedPackPage::suggestVersion();
} }
@ -420,7 +421,7 @@ void FlameManagedPackPage::parseManagedPack()
"Don't worry though, it will ask you to update this instance instead, so you'll not lose this instance!" "Don't worry though, it will ask you to update this instance instead, so you'll not lose this instance!"
"</h4>"); "</h4>");
ui->changelogTextBrowser->setHtml(message); ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(message));
return; return;
} }
@ -502,7 +503,8 @@ void FlameManagedPackPage::suggestVersion()
} }
auto version = m_pack.versions.at(index); auto version = m_pack.versions.at(index);
ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)); ui->changelogTextBrowser->setHtml(
StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)));
ManagedPackPage::suggestVersion(); ManagedPackPage::suggestVersion();
} }

View File

@ -37,6 +37,7 @@
*/ */
#include "ModFolderPage.h" #include "ModFolderPage.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
@ -121,6 +122,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage); ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata);
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
@ -372,3 +376,15 @@ void ModFolderPage::deleteModMetadata()
m_model->deleteModsMetadata(selection); m_model->deleteModsMetadata(selection);
} }
void ModFolderPage::exportModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectedMods = m_model->selectedMods(selection);
if (selectedMods.length() == 0)
selectedMods = m_model->allMods();
std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); });
ExportToModListDialog dlg(m_instance->name(), selectedMods, this);
dlg.exec();
}

View File

@ -62,6 +62,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots: private slots:
void removeItems(const QItemSelection& selection) override; void removeItems(const QItemSelection& selection) override;
void deleteModMetadata(); void deleteModMetadata();
void exportModMetadata();
void installMods(); void installMods();
void updateMods(bool includeDeps = false); void updateMods(bool includeDeps = false);

View File

@ -45,6 +45,7 @@
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h" #include "ui/pages/modplatform/ResourceModel.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
@ -234,8 +235,8 @@ void ResourcePage::updateUi()
text += "<hr>"; text += "<hr>";
m_ui->packDescription->setHtml( m_ui->packDescription->setHtml(StringUtils::htmlListPatch(
text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))); text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))));
m_ui->packDescription->flush(); m_ui->packDescription->flush();
} }

View File

@ -39,6 +39,7 @@
#include "ui_AtlPage.h" #include "ui_AtlPage.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "StringUtils.h"
#include "AtlUserInteractionSupportImpl.h" #include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h" #include "modplatform/atlauncher/ATLPackInstallTask.h"
@ -144,7 +145,7 @@ void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex
selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>(); selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>();
ui->packDescription->setHtml(selected.description.replace("\n", "<br>")); ui->packDescription->setHtml(StringUtils::htmlListPatch(selected.description.replace("\n", "<br>")));
for (const auto& version : selected.versions) { for (const auto& version : selected.versions) {
ui->versionSelectionBox->addItem(version.version); ui->versionSelectionBox->addItem(version.version);

View File

@ -43,6 +43,7 @@
#include "FlameModel.h" #include "FlameModel.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "Json.h" #include "Json.h"
#include "StringUtils.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
@ -292,6 +293,6 @@ void FlamePage::updateUi()
text += "<hr>"; text += "<hr>";
text += api.getModDescription(current.addonId).toUtf8(); text += api.getModDescription(current.addonId).toUtf8();
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
ui->packDescription->flush(); ui->packDescription->flush();
} }

View File

@ -35,6 +35,7 @@
*/ */
#include "Page.h" #include "Page.h"
#include "StringUtils.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "ui_Page.h" #include "ui_Page.h"
@ -260,8 +261,9 @@ void Page::onPackSelectionChanged(Modpack* pack)
{ {
ui->versionSelectionBox->clear(); ui->versionSelectionBox->clear();
if (pack) { if (pack) {
currentModpackInfo->setHtml("Pack by <b>" + pack->author + "</b>" + "<br>Minecraft " + pack->mcVersion + "<br>" + "<br>" + currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by <b>" + pack->author + "</b>" + "<br>Minecraft " + pack->mcVersion +
pack->description + "<ul><li>" + pack->mods.replace(";", "</li><li>") + "</li></ul>"); "<br>" + "<br>" + pack->description + "<ul><li>" +
pack->mods.replace(";", "</li><li>") + "</li></ul>"));
bool currentAdded = false; bool currentAdded = false;
for (int i = 0; i < pack->oldVersions.size(); i++) { for (int i = 0; i < pack->oldVersions.size(); i++) {

View File

@ -44,6 +44,7 @@
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "Json.h" #include "Json.h"
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
@ -304,7 +305,7 @@ void ModrinthPage::updateUI()
text += markdownToHTML(current.extra.body.toUtf8()); text += markdownToHTML(current.extra.body.toUtf8());
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
ui->packDescription->flush(); ui->packDescription->flush();
} }

View File

@ -44,6 +44,7 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "StringUtils.h"
#include "TechnicModel.h" #include "TechnicModel.h"
#include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SingleZipPackInstallTask.h"
#include "modplatform/technic/SolderPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h"
@ -233,7 +234,7 @@ void TechnicPage::metadataLoaded()
text += "<br><br>"; text += "<br><br>";
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
// Strip trailing forward-slashes from Solder URL's // Strip trailing forward-slashes from Solder URL's
if (current.isSolder) { if (current.isSolder) {

View File

@ -26,6 +26,7 @@
#include <QTextBrowser> #include <QTextBrowser>
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h"
SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList<GitHubRelease>& releases, QWidget* parent) SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList<GitHubRelease>& releases, QWidget* parent)
: QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog) : QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog)
@ -96,7 +97,7 @@ void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidget
QString body = markdownToHTML(release.body.toUtf8()); QString body = markdownToHTML(release.body.toUtf8());
m_selectedRelease = release; m_selectedRelease = release;
ui->changelogTextBrowser->setHtml(body); ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(body));
} }
SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent) SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent)

View File

@ -18,7 +18,7 @@
jdk21, jdk21,
gamemode, gamemode,
flite, flite,
mesa-demos, glxinfo,
udev, udev,
libusb1, libusb1,
msaClientID ? null, msaClientID ? null,
@ -81,7 +81,7 @@ in
runtimePrograms = runtimePrograms =
[ [
xorg.xrandr xorg.xrandr
mesa-demos # need glxinfo glxinfo
] ]
++ additionalPrograms; ++ additionalPrograms;
in in