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

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2023-09-01 21:36:28 +03:00
commit e095780cc3
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
103 changed files with 1222 additions and 550 deletions

1
.envrc
View File

@ -1 +1,2 @@
use flake use flake
watch_file nix/*.nix

View File

@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v22
- uses: DeterminateSystems/update-flake-lock@v19 - uses: DeterminateSystems/update-flake-lock@v20
with: with:
commit-msg: "chore(nix): update lockfile" commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile"

View File

@ -91,11 +91,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1692463654, "lastModified": 1693145325,
"narHash": "sha256-F8hZmsQINI+S6UROM4jyxAMbQLtzE44pI8Nk6NtMdao=", "narHash": "sha256-Gat9xskErH1zOcLjYMhSDBo0JTBZKfGS0xJlIRnj6Rc=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "ca3c9ac9f4cdd4bea19f592b32bb59b74ab7d783", "rev": "cddebdb60de376c1bdb7a4e6ee3d98355453fe56",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -594,6 +594,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true); m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true); m_settings->registerSetting("RecordGameTime", true);
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods // Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModMetadataDisabled", false);

View File

@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{ {
auto fileName = fileInfo.fileName(); return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
auto path = relPath(fileInfo.absoluteFilePath());
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
m_ignoreFilePaths.covers(path);
} }
bool FileIgnoreProxy::filterFile(const QString& fileName) const bool FileIgnoreProxy::filterFile(const QString& fileName) const

View File

@ -16,19 +16,20 @@
*/ */
#include <MMCTime.h> #include <MMCTime.h>
#include <qobject.h>
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QTextStream> #include <QTextStream>
QString Time::prettifyDuration(int64_t duration) QString Time::prettifyDuration(int64_t duration, bool noDays)
{ {
int seconds = (int)(duration % 60); int seconds = (int)(duration % 60);
duration /= 60; duration /= 60;
int minutes = (int)(duration % 60); int minutes = (int)(duration % 60);
duration /= 60; duration /= 60;
int hours = (int)(duration % 24); int hours = (int)(noDays ? duration : (duration % 24));
int days = (int)(duration / 24); int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) { if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds); return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
} }

View File

@ -20,7 +20,7 @@
namespace Time { namespace Time {
QString prettifyDuration(int64_t duration); QString prettifyDuration(int64_t duration, bool noDays = false);
/** /**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`. * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.

View File

@ -103,14 +103,8 @@ class Version {
QString m_fullString; QString m_fullString;
[[nodiscard]] inline bool isAppendix() const [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
{ [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
return m_stringPart.startsWith('+');
}
[[nodiscard]] inline bool isPreRelease() const
{
return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
}
inline bool operator==(const Section& other) const inline bool operator==(const Section& other) const
{ {
@ -156,14 +150,8 @@ class Version {
return m_fullString < other.m_fullString; return m_fullString < other.m_fullString;
} }
inline bool operator!=(const Section& other) const inline bool operator!=(const Section& other) const { return !(*this == other); }
{ inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
return !(*this == other);
}
inline bool operator>(const Section& other) const
{
return !(*this < other || *this == other);
}
}; };
private: private:

View File

@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
enum Roles { LevelRole = Qt::UserRole }; enum Roles { LevelRole = Qt::UserRole };
private /* types */: private /* types */:
struct entry { struct entry {
MessageLevel::Enum level; MessageLevel::Enum level;
QString line; QString line;

View File

@ -2,14 +2,14 @@
#include "Component.h" #include "Component.h"
#include "ComponentUpdateTask_p.h" #include "ComponentUpdateTask_p.h"
#include "OneSixVersionFormat.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "Version.h" #include "Version.h"
#include "cassert" #include "cassert"
#include "meta/Index.h" #include "meta/Index.h"
#include "meta/Version.h" #include "meta/Version.h"
#include "meta/VersionList.h" #include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "Application.h" #include "Application.h"

View File

@ -195,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false); m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", ""); m_settings->registerSetting("InstanceAccountId", "");
m_settings->registerSetting("ExportName", "");
m_settings->registerSetting("ExportVersion", "1.0.0");
m_settings->registerSetting("ExportSummary", "");
m_settings->registerSetting("ExportAuthor", "");
m_settings->registerSetting("ExportOptionalFiles", true);
qDebug() << "Instance-type specific settings were loaded!"; qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true); setSpecificSettingsLoaded(true);
@ -934,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) { if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) { if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(tr(", last played on %1 for %2") description.append(
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) tr(", last played on %1 for %2")
.arg(Time::prettifyDuration(lastTimePlayed()))); .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
} }
if (totalTimePlayed() > 0) { if (totalTimePlayed() > 0) {
description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); description.append(
tr(", total played for %1")
.arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
} }
} }
if (hasCrashed()) { if (hasCrashed()) {

View File

@ -58,15 +58,14 @@
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
#include "Application.h" static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
#include "modplatform/ResourceAPI.h" { "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge }, { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
{ "net.minecraftforge", ResourceAPI::Forge }, { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel() PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{ {
@ -990,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
} }
} }
std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders() std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
{ {
ResourceAPI::ModLoaderTypes result; ModPlatform::ModLoaderTypes result;
bool has_any_loader = false; bool has_any_loader = false;
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping); QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) { while (i.hasNext()) {
i.next(); i.next();
@ -1009,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {}; return {};
return result; return result;
} }
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
{
auto loadersOpt = getModLoaders();
if (!loadersOpt.has_value())
return loadersOpt;
auto loaders = loadersOpt.value();
// TODO: remove this or add version condition once Quilt drops official Fabric support
if (loaders & ModPlatform::Quilt)
loaders |= ModPlatform::Fabric;
// TODO: remove this or add version condition once NeoForge drops official Forge support
if (loaders & ModPlatform::NeoForge)
loaders |= ModPlatform::Forge;
return loaders;
}

View File

@ -44,14 +44,11 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <memory> #include <memory>
#include <optional>
#include "BaseVersion.h"
#include "Component.h" #include "Component.h"
#include "LaunchProfile.h" #include "LaunchProfile.h"
#include "Library.h" #include "modplatform/ModIndex.h"
#include "MojangDownloadInfo.h"
#include "ProfileUtils.h"
#include "modplatform/ResourceAPI.h"
#include "net/Mode.h" #include "net/Mode.h"
class MinecraftInstance; class MinecraftInstance;
@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
// todo(merged): is this the best approach // todo(merged): is this the best approach
void appendComponent(ComponentPtr component); void appendComponent(ComponentPtr component);
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders(); std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
private: private:
void scheduleSave(); void scheduleSave();

View File

@ -51,8 +51,13 @@
#include "Application.h" #include "Application.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@ -309,3 +314,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
} }
static const FlameAPI flameAPI;
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = flameAPI.getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return ResourceFolderModel::installResource(file_path);
}

View File

@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance; class LegacyInstance;
class BaseInstance; class BaseInstance;
@ -75,6 +76,7 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false); bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods /// Deletes all the selected mods

View File

@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion(); return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
} }
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
{ {
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value(); return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
} }
GetModDependenciesTask::GetModDependenciesTask(QObject* parent, GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName) const ModPlatform::ResourceProvider providerName)
{ {
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) { if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
auto overide = ModPlatform::getOverrideDeps(); auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) { auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt); return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
} }
pDep->version = provider.mod->loadDependencyVersions(dep, arr); pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) { if (!pDep->version.addonId.isValid()) {
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps(); auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; }); [dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return; return;
} }
} }
removePack(dep.addonId);
qWarning() << "Error while reading mod version empty "; qWarning() << "Error while reading mod version empty ";
qDebug() << doc; qDebug() << doc;
return; return;

View File

@ -80,5 +80,5 @@ class GetModDependenciesTask : public SequentialTask {
Provider m_modrinth_provider; Provider m_modrinth_provider;
Version m_version; Version m_version;
ResourceAPI::ModLoaderTypes m_loaderType; ModPlatform::ModLoaderTypes m_loaderType;
}; };

View File

@ -14,7 +14,7 @@ class CheckUpdateTask : public Task {
public: public:
CheckUpdateTask(QList<Mod*>& mods, CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
@ -53,7 +53,7 @@ class CheckUpdateTask : public Task {
protected: protected:
QList<Mod*>& m_mods; QList<Mod*>& m_mods;
std::list<Version>& m_game_versions; std::list<Version>& m_game_versions;
std::optional<ResourceAPI::ModLoaderTypes> m_loaders; std::optional<ModPlatform::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder; std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable; std::vector<UpdatableMod> m_updatable;

View File

@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString(); projectID.toString();
} }
auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case NeoForge:
return "neoforge";
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
} // namespace ModPlatform } // namespace ModPlatform

View File

@ -30,6 +30,9 @@ class QIODevice;
namespace ModPlatform { namespace ModPlatform {
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
enum class ResourceProvider { MODRINTH, FLAME }; enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@ -70,7 +73,7 @@ struct IndexedVersion {
QString downloadUrl; QString downloadUrl;
QString date; QString date;
QString fileName; QString fileName;
QStringList loaders = {}; ModLoaderTypes loaders = {};
QString hash_type; QString hash_type;
QString hash; QString hash;
bool is_preferred = true; bool is_preferred = true;
@ -128,7 +131,6 @@ struct IndexedPack {
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
} }
}; };
QString getMetaURL(ResourceProvider provider, QVariant projectID);
struct OverrideDep { struct OverrideDep {
QString quilt; QString quilt;
@ -148,6 +150,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
QString getMetaURL(ResourceProvider provider, QVariant projectID); QString getMetaURL(ResourceProvider provider, QVariant projectID);
auto getModLoaderString(ModLoaderType type) -> const QString;
constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept
{
auto x = static_cast<int>(l);
return x && !(x & (x - 1));
}
} // namespace ModPlatform } // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack) Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -54,9 +54,6 @@ class ResourceAPI {
public: public:
virtual ~ResourceAPI() = default; virtual ~ResourceAPI() = default;
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SortingMethod { struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request. // Used by Flame in the API request.
@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search; std::optional<QString> search;
std::optional<SortingMethod> sorting; std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders; std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions; std::optional<std::list<Version> > versions;
}; };
struct SearchCallbacks { struct SearchCallbacks {
@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions; std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders; std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default; VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other) void operator=(VersionSearchArgs other)
@ -108,13 +105,15 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; } void operator=(ProjectInfoArgs other) { pack = other.pack; }
}; };
struct ProjectInfoCallbacks { struct ProjectInfoCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
std::function<void(QString const& reason)> on_fail;
std::function<void()> on_abort;
}; };
struct DependencySearchArgs { struct DependencySearchArgs {
ModPlatform::Dependency dependency; ModPlatform::Dependency dependency;
Version mcVersion; Version mcVersion;
ModLoaderTypes loader; ModPlatform::ModLoaderTypes loader;
}; };
struct DependencySearchCallbacks { struct DependencySearchCallbacks {
@ -161,27 +160,6 @@ class ResourceAPI {
return nullptr; return nullptr;
} }
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case NeoForge:
return "neoforge";
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
protected: protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; } [[nodiscard]] inline QString debugName() const { return "External resource API"; }

View File

@ -1,6 +1,7 @@
#include "FileResolvingTask.h" #include "FileResolvingTask.h"
#include "Json.h" #include "Json.h"
#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
#include "net/Upload.h" #include "net/Upload.h"
@ -102,7 +103,7 @@ void Flame::FileResolvingTask::netJobFinished()
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output); auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; }); QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl); m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output); blockedProjects.insert(&out, output);
@ -153,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
// If there's more than one mod loader for this version, we can't know for sure // If there's more than one mod loader for this version, we can't know for sure
// which file is relative to each loader, so it's best to not use any one and // which file is relative to each loader, so it's best to not use any one and
// let the user download it manually. // let the user download it manually.
if (file.loaders.size() <= 1) { if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl; out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName; qDebug() << "Found alternative on modrinth " << out->fileName;
} else { } else {
@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::ApiDownload::makeByteArray(url, output); auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName; qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output); auto json = QJsonDocument::fromJson(*output);
auto base = auto base =

View File

@ -6,7 +6,6 @@
#include "FlameModIndex.h" #include "FlameModIndex.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto obj = Json::requireObject(doc); auto obj = Json::requireObject(doc);
auto arr = Json::requireArray(obj, "data"); auto arr = Json::requireArray(obj, "data");
QJsonObject latest_file_obj;
ModPlatform::IndexedVersion ver_tmp;
for (auto file : arr) { for (auto file : arr) {
auto file_obj = Json::requireObject(file); auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
if (file_tmp.date > ver_tmp.date) { if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
ver_tmp = file_tmp; ver = file_tmp;
latest_file_obj = file_obj;
}
} }
ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qCritical() << "Failed to parse response from a version request."; qCritical() << "Failed to parse response from a version request.";
qCritical() << e.what(); qCritical() << e.what();

View File

@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (NeoForge | Forge | Fabric | Quilt); } static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
{
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt);
}
private: private:
static int getClassId(ModPlatform::ResourceType type) static int getClassId(ModPlatform::ResourceType type)
@ -38,22 +41,42 @@ class FlameAPI : public NetworkResourceAPI {
} }
} }
static int getMappedModLoader(ModLoaderTypes loaders) static int getMappedModLoader(ModPlatform::ModLoaderType loaders)
{ {
// https://docs.curseforge.com/?http#tocS_ModLoaderType // https://docs.curseforge.com/?http#tocS_ModLoaderType
if (loaders & Forge) switch (loaders) {
return 1; case ModPlatform::Forge:
if (loaders & Fabric) return 1;
return 4; case ModPlatform::Cauldron:
// TODO: remove this once Quilt drops official Fabric support return 2;
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently* case ModPlatform::LiteLoader:
return 4; // FIXME: implement multiple loaders filter (this should be 5) return 3;
// TODO: remove this once NeoForge drops official Forge support case ModPlatform::Fabric:
if (loaders & NeoForge) // NOTE: Most if not all Forge mods should work *currently* return 4;
return 1; // FIXME: implement multiple loaders filter (this should be 6) case ModPlatform::Quilt:
return 5;
case ModPlatform::NeoForge:
return 6;
}
return 0; return 0;
} }
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
{
QStringList l;
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
if (types & loader) {
l << QString::number(getMappedModLoader(loader));
}
}
return l;
}
static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{
return "[" + getModLoaderStrings(types).join(',') + "]";
}
private: private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{ {
@ -70,7 +93,7 @@ class FlameAPI : public NetworkResourceAPI {
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc"); get_arguments.append("sortOrder=desc");
if (args.loaders.has_value()) if (args.loaders.has_value())
get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
get_arguments.append(gameVersionStr); get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
@ -84,47 +107,27 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{ {
auto addonId = args.pack.addonId.toString(); auto addonId = args.pack.addonId.toString();
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) }; QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId);
QStringList get_parameters;
if (args.mcVersions.has_value()) if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
if (args.loaders.has_value()) { if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
int mappedModLoader = getMappedModLoader(args.loaders.value()); int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
url += QString("&modLoaderType=%1").arg(mappedModLoader);
if (args.loaders.value() & Quilt) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
});
if (over != overide.cend()) {
mappedModLoader = 5;
}
}
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
} }
return url;
return url + get_parameters.join('&');
}; };
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{ {
auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString(); auto addonId = args.dependency.addonId.toString();
if (args.loader & Quilt) { auto url =
auto overide = ModPlatform::getOverrideDeps(); QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) { if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt; int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
}); url += QString("&modLoaderType=%1").arg(mappedModLoader);
if (over != overide.cend()) {
mappedModLoader = 5;
}
} }
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3") return url;
.arg(addonId)
.arg(args.mcVersion.toString())
.arg(mappedModLoader);
}; };
}; };

View File

@ -5,13 +5,11 @@
#include <MurmurHash2.h> #include <MurmurHash2.h>
#include <memory> #include <memory>
#include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "ResourceDownloadTask.h" #include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"

View File

@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
public: public:
FlameCheckUpdate(QList<Mod*>& mods, FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{} {}

View File

@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QVector<ModPlatform::IndexedVersion> unsortedVersions; QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft"); QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid()) if (!file.addonId.isValid())
file.addonId = pack.addonId; file.addonId = pack.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file); unsortedVersions.append(file);
} }
@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.')) if (str.contains('.'))
file.mcVersion.append(str); file.mcVersion.append(str);
auto loader = str.toLower();
if (loader == "neoforge")
file.loaders |= ModPlatform::NeoForge;
if (loader == "forge")
file.loaders |= ModPlatform::Forge;
if (loader == "cauldron")
file.loaders |= ModPlatform::Cauldron;
if (loader == "liteloader")
file.loaders |= ModPlatform::LiteLoader;
if (loader == "fabric")
file.loaders |= ModPlatform::Fabric;
if (loader == "quilt")
file.loaders |= ModPlatform::Quilt;
} }
file.addonId = Json::requireInteger(obj, "modId"); file.addonId = Json::requireInteger(obj, "modId");
@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
return file; return file;
} }
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
{ {
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
QVector<ModPlatform::IndexedVersion> versions; QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid()) if (!file.addonId.isValid())
file.addonId = m.addonId; file.addonId = m.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file); versions.append(file);
} }
@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date; return a.date > b.date;
}; };
std::sort(versions.begin(), versions.end(), orderSortPredicate); std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.front(); if (versions.size() != 0)
return versions.front();
return {};
} }

View File

@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst); const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion; auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace FlameMod } // namespace FlameMod

View File

@ -43,12 +43,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
FlamePackExportTask::FlamePackExportTask(const QString& name, FlamePackExportTask::FlamePackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& author, const QString& author,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter) MMCZip::FilterFunction filter)
: name(name) : name(name)
, version(version) , version(version)
, author(author) , author(author)
, optionalFiles(optionalFiles)
, instance(instance) , instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot()) , gameRoot(instance->gameRoot())
@ -410,7 +412,7 @@ QByteArray FlamePackExportTask::generateIndex()
QJsonObject file; QJsonObject file;
file["projectID"] = mod.addonId; file["projectID"] = mod.addonId;
file["fileID"] = mod.version; file["fileID"] = mod.version;
file["required"] = mod.enabled; file["required"] = mod.enabled || !optionalFiles;
files << file; files << file;
} }
obj["files"] = files; obj["files"] = files;

View File

@ -30,6 +30,7 @@ class FlamePackExportTask : public Task {
FlamePackExportTask(const QString& name, FlamePackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& author, const QString& author,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter); MMCZip::FilterFunction filter);
@ -44,6 +45,7 @@ class FlamePackExportTask : public Task {
// inputs // inputs
const QString name, version, author; const QString name, version, author;
const bool optionalFiles;
const InstancePtr instance; const InstancePtr instance;
MinecraftInstance* mcInstance; MinecraftInstance* mcInstance;
const QDir gameRoot; const QDir gameRoot;

View File

@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
callbacks.on_succeed(doc, args.pack); callbacks.on_succeed(doc, args.pack);
}); });
QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); });
QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
return job; return job;
} }
@ -131,7 +132,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [=] { QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -60,19 +60,19 @@ Modpack parseDirectory(QString path)
auto name = Json::requireString(obj, "name", "name"); auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version"); auto version = Json::requireString(obj, "version", "version");
if (name == "neoforge") { if (name == "neoforge") {
modpack.loaderType = ResourceAPI::NeoForge; modpack.loaderType = ModPlatform::NeoForge;
modpack.version = version; modpack.version = version;
break; break;
} else if (name == "forge") { } else if (name == "forge") {
modpack.loaderType = ResourceAPI::Forge; modpack.loaderType = ModPlatform::Forge;
modpack.version = version; modpack.version = version;
break; break;
} else if (name == "fabric") { } else if (name == "fabric") {
modpack.loaderType = ResourceAPI::Fabric; modpack.loaderType = ModPlatform::Fabric;
modpack.version = version; modpack.version = version;
break; break;
} else if (name == "quilt") { } else if (name == "quilt") {
modpack.loaderType = ResourceAPI::Quilt; modpack.loaderType = ModPlatform::Quilt;
modpack.version = version; modpack.version = version;
break; break;
} }

View File

@ -39,7 +39,7 @@ struct Modpack {
// not needed for instance creation // not needed for instance creation
QVariant jvmArgs; QVariant jvmArgs;
std::optional<ResourceAPI::ModLoaderType> loaderType; std::optional<ModPlatform::ModLoaderType> loaderType;
QString loaderVersion; QString loaderVersion;
QIcon icon; QIcon icon;

View File

@ -68,25 +68,25 @@ void PackInstallTask::copySettings()
auto modloader = m_pack.loaderType; auto modloader = m_pack.loaderType;
if (modloader.has_value()) if (modloader.has_value())
switch (modloader.value()) { switch (modloader.value()) {
case ResourceAPI::NeoForge: { case ModPlatform::NeoForge: {
components->setComponentVersion("net.neoforged", m_pack.version, true); components->setComponentVersion("net.neoforged", m_pack.version, true);
break; break;
} }
case ResourceAPI::Forge: { case ModPlatform::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true); components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break; break;
} }
case ResourceAPI::Fabric: { case ModPlatform::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break; break;
} }
case ResourceAPI::Quilt: { case ModPlatform::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break; break;
} }
case ResourceAPI::Cauldron: case ModPlatform::Cauldron:
break; break;
case ResourceAPI::LiteLoader: case ModPlatform::LiteLoader:
break; break;
} }
components->saveNow(); components->saveNow();

View File

@ -41,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
Task::Ptr ModrinthAPI::latestVersion(QString hash, Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@ -71,7 +71,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());

View File

@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersion(QString hash, auto latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes, auto latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
@ -35,22 +35,19 @@ class ModrinthAPI : public NetworkResourceAPI {
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
{ {
QStringList l; QStringList l;
for (auto loader : { NeoForge, Forge, Fabric, Quilt, LiteLoader }) { for (auto loader :
{ ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) {
if (types & loader) { if (types & loader) {
l << getModLoaderString(loader); l << getModLoaderString(loader);
} }
} }
if ((types & NeoForge) && (~types & Forge)) // Add Forge if NeoForge is in use, if Forge isn't already there
l << getModLoaderString(Forge);
if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
l << getModLoaderString(Fabric);
return l; return l;
} }
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{ {
QStringList l; QStringList l;
for (auto loader : getModLoaderStrings(types)) { for (auto loader : getModLoaderStrings(types)) {
@ -143,9 +140,9 @@ class ModrinthAPI : public NetworkResourceAPI {
return s.isEmpty() ? QString() : s; return s.isEmpty() ? QString() : s;
} }
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
{ {
return loaders & (NeoForge | Forge | Fabric | Quilt | LiteLoader); return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader);
} }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override

View File

@ -11,7 +11,6 @@
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask()
// so we may want to filter it // so we may want to filter it
QString loader_filter; QString loader_filter;
if (m_loaders.has_value()) { if (m_loaders.has_value()) {
static auto flags = { ResourceAPI::ModLoaderType::NeoForge, ResourceAPI::ModLoaderType::Forge, static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
for (auto flag : flags) { for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) { if (m_loaders.value().testFlag(flag)) {
loader_filter = api.getModLoaderString(flag); loader_filter = ModPlatform::getModLoaderString(flag);
break; break;
} }
} }

View File

@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public: public:
ModrinthCheckUpdate(QList<Mod*>& mods, ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{} {}

View File

@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& summary, const QString& summary,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter) MMCZip::FilterFunction filter)
: name(name) : name(name)
, version(version) , version(version)
, summary(summary) , summary(summary)
, optionalFiles(optionalFiles)
, instance(instance) , instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot()) , gameRoot(instance->gameRoot())
@ -270,16 +272,18 @@ QByteArray ModrinthPackExportTask::generateIndex()
QString path = iterator.key(); QString path = iterator.key();
const ResolvedFile& value = iterator.value(); const ResolvedFile& value = iterator.value();
// detect disabled mod if (optionalFiles) {
const QFileInfo pathInfo(path); // detect disabled mod
if (pathInfo.suffix() == "disabled") { const QFileInfo pathInfo(path);
// rename it if (pathInfo.suffix() == "disabled") {
path = pathInfo.dir().filePath(pathInfo.completeBaseName()); // rename it
// ...and make it optional path = pathInfo.dir().filePath(pathInfo.completeBaseName());
QJsonObject env; // ...and make it optional
env["client"] = "optional"; QJsonObject env;
env["server"] = "optional"; env["client"] = "optional";
fileOut["env"] = env; env["server"] = "optional";
fileOut["env"] = env;
}
} }
fileOut["path"] = path; fileOut["path"] = path;

View File

@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task {
ModrinthPackExportTask(const QString& name, ModrinthPackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& summary, const QString& summary,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter); MMCZip::FilterFunction filter);
@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task {
// inputs // inputs
const QString name, version, summary; const QString name, version, summary;
const bool optionalFiles;
const InstancePtr instance; const InstancePtr instance;
MinecraftInstance* mcInstance; MinecraftInstance* mcInstance;
const QDir gameRoot; const QDir gameRoot;

View File

@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true; pack.extraDataLoaded = true;
} }
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
QJsonArray& arr,
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst)
{ {
QVector<ModPlatform::IndexedVersion> unsortedVersions; QVector<ModPlatform::IndexedVersion> unsortedVersions;
QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft"); auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); auto file = loadIndexedPackVersion(obj);
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file); unsortedVersions.append(file);
} }
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@ -134,7 +134,18 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
} }
auto loaders = Json::requireArray(obj, "loaders"); auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : loaders) { for (auto loader : loaders) {
file.loaders.append(loader.toString()); if (loader == "neoforge")
file.loaders |= ModPlatform::NeoForge;
if (loader == "forge")
file.loaders |= ModPlatform::Forge;
if (loader == "cauldron")
file.loaders |= ModPlatform::Cauldron;
if (loader == "liteloader")
file.loaders |= ModPlatform::LiteLoader;
if (loader == "fabric")
file.loaders |= ModPlatform::Fabric;
if (loader == "quilt")
file.loaders |= ModPlatform::Quilt;
} }
file.version = Json::requireString(obj, "name"); file.version = Json::requireString(obj, "name");
file.version_number = Json::requireString(obj, "version_number"); file.version_number = Json::requireString(obj, "version_number");
@ -218,15 +229,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {}; return {};
} }
auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
-> ModPlatform::IndexedVersion
{ {
QVector<ModPlatform::IndexedVersion> versions; auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); auto file = loadIndexedPackVersion(obj);
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file); versions.append(file);
} }
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {

View File

@ -26,11 +26,8 @@ namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion; auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace Modrinth } // namespace Modrinth

View File

@ -43,7 +43,6 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include <QDir> #include <QDir>
@ -90,17 +89,14 @@
#include <news/NewsChecker.h> #include <news/NewsChecker.h>
#include <tools/BaseProfiler.h> #include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h> #include <updater/ExternalUpdater.h>
#include "InstancePageProvider.h"
#include "InstanceWindow.h" #include "InstanceWindow.h"
#include "JavaCommon.h"
#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.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"
@ -113,17 +109,22 @@
#include "ui/themes/ThemeManager.h" #include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h" #include "ui/widgets/LabeledToolButton.h"
#include "minecraft/PackProfile.h"
#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h" #include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h" #include "minecraft/mod/tasks/LocalResourceParse.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "KonamiCode.h" #include "KonamiCode.h"
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
#include "Json.h" #include "Json.h"
@ -924,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty()) if (url.scheme().isEmpty())
url.setScheme("file"); url.setScheme("file");
ModPlatform::IndexedVersion version;
QMap<QString, QString> extra_info; QMap<QString, QString> extra_info;
QUrl local_url; QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify if (!url.isLocalFile()) { // download the remote resource and identify
@ -949,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
auto api = FlameAPI(); auto api = FlameAPI();
auto job = api.getFile(addonId, fileId, array); auto job = api.getFile(addonId, fileId, array);
QString resource_name;
connect(job.get(), &Task::failed, this, connect(job.get(), &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] { connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] {
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
auto doc = Json::requireDocument(*array); auto doc = Json::requireDocument(*array);
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
// No way to find out if it's a mod or a modpack before here // No way to find out if it's a mod or a modpack before here
// And also we need to check if it ends with .zip, instead of any better way // And also we need to check if it ends with .zip, instead of any better way
auto fileName = Json::ensureString(data, "fileName"); version = FlameMod::loadIndexedPackVersion(data);
auto fileName = version.fileName;
// Have to use ensureString then use QUrl to get proper url encoding // Have to use ensureString then use QUrl to get proper url encoding
dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl")); dl_url = QUrl(version.downloadUrl);
if (!dl_url.isValid()) { if (!dl_url.isValid()) {
CustomMessageBox::selectable( CustomMessageBox::selectable(
this, tr("Error"), this, tr("Error"),
@ -973,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
} }
QFileInfo dl_file(dl_url.fileName()); QFileInfo dl_file(dl_url.fileName());
resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
}); });
{ // drop stack { // drop stack
@ -1048,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break; break;
case PackedResourceType::Mod: case PackedResourceType::Mod:
minecraftInst->loaderModList()->installMod(localFileName); minecraftInst->loaderModList()->installMod(localFileName, version);
break; break;
case PackedResourceType::ShaderPack: case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName); minecraftInst->shaderPackList()->installResource(localFileName);
@ -1735,7 +1735,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime(); int timePlayed = APPLICATION->instances()->getTotalPlayTime();
if (timePlayed > 0) { if (timePlayed > 0) {
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); m_statusCenter->setText(
tr("Total playtime: %1")
.arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
} }
} }
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here) // "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)

View File

@ -151,10 +151,7 @@ class MainWindow : public QMainWindow {
void deleteGroup(); void deleteGroup();
void undoTrashInstance(); void undoTrashInstance();
inline void on_actionExportInstance_triggered() inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
{
on_actionExportInstanceZip_triggered();
}
void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered(); void on_actionExportInstanceFlamePack_triggered();

View File

@ -37,15 +37,21 @@
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider) : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
{ {
Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
ui->setupUi(this); ui->setupUi(this);
ui->name->setText(instance->name()); ui->name->setPlaceholderText(instance->name());
ui->name->setText(instance->settings()->get("ExportName").toString());
ui->version->setText(instance->settings()->get("ExportVersion").toString());
ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); setWindowTitle(tr("Export Modrinth Pack"));
setWindowTitle("Export Modrinth Pack"); ui->summary->setText(instance->settings()->get("ExportSummary").toString());
} else { } else {
setWindowTitle("Export CurseForge Pack"); setWindowTitle(tr("Export CurseForge Pack"));
ui->version->setText(""); ui->summaryLabel->setText(tr("&Author"));
ui->summaryLabel->setText("Author"); ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
} }
// ensure a valid pack is generated // ensure a valid pack is generated
@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
if (mcInstance) { if (mcInstance) {
mcInstance->loaderModList()->update();
const QDir index = mcInstance->loaderModList()->indexDir(); const QDir index = mcInstance->loaderModList()->indexDir();
if (index.exists()) if (index.exists())
proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
} }
ui->treeView->setModel(proxy); ui->files->setModel(proxy);
ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
ui->treeView->sortByColumn(0, Qt::AscendingOrder); ui->files->sortByColumn(0, Qt::AscendingOrder);
model->setFilter(filter); model->setFilter(filter);
model->setRootPath(instance->gameRoot()); model->setRootPath(instance->gameRoot());
QHeaderView* headerView = ui->treeView->header(); QHeaderView* headerView = ui->files->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch); headerView->setSectionResizeMode(0, QHeaderView::Stretch);
} }
@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
void ExportPackDialog::done(int result) void ExportPackDialog::done(int result)
{ {
if (result == Accepted) { auto settings = instance->settings();
const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); settings->set("ExportName", ui->name->text());
QString output; settings->set("ExportVersion", ui->version->text());
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
nullptr); if (result == Accepted) {
else const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), const QString filename = FS::RemoveInvalidFilenameChars(name);
FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
QString output;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
"Modrinth pack (*.mrpack *.zip)", nullptr);
if (output.isEmpty())
return;
if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
output.append(".mrpack");
} else {
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
"CurseForge pack (*.zip)", nullptr);
if (output.isEmpty())
return;
if (!output.endsWith(".zip"))
output.append(".zip");
}
if (output.isEmpty())
return;
Task* task; Task* task;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
else } else {
task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
}
connect(task, &Task::failed, connect(task, &Task::failed,
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
@ -140,7 +160,6 @@ void ExportPackDialog::done(int result)
void ExportPackDialog::validate() void ExportPackDialog::validate()
{ {
const bool invalid = ui->buttonBox->button(QDialogButtonBox::Ok)
ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty()); ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
} }

View File

@ -7,12 +7,9 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>650</width> <width>650</width>
<height>413</height> <height>510</height>
</rect> </rect>
</property> </property>
<property name="windowTitle">
<string>Export Pack</string>
</property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -20,13 +17,16 @@
<item> <item>
<widget class="QGroupBox" name="information"> <widget class="QGroupBox" name="information">
<property name="title"> <property name="title">
<string>Information</string> <string>&amp;Description</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="summaryLabel"> <widget class="QLabel" name="summaryLabel">
<property name="text"> <property name="text">
<string>Summary</string> <string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -36,14 +36,20 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="nameLabel"> <widget class="QLabel" name="nameLabel">
<property name="text"> <property name="text">
<string>Name</string> <string>&amp;Name</string>
</property>
<property name="buddy">
<cstring>name</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="versionLabel"> <widget class="QLabel" name="versionLabel">
<property name="text"> <property name="text">
<string>Version</string> <string>&amp;Version</string>
</property>
<property name="buddy">
<cstring>version</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -57,31 +63,52 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="filesLabel"> <widget class="QGroupBox" name="options">
<property name="text"> <property name="title">
<string>Files</string> <string>&amp;Options</string>
</property> </property>
</widget> <layout class="QVBoxLayout" name="verticalLayout">
</item> <item>
<item> <widget class="QLabel" name="filesLabel">
<widget class="QTreeView" name="treeView"> <property name="text">
<property name="alternatingRowColors"> <string>&amp;Files</string>
<bool>true</bool> </property>
</property> <property name="buddy">
<property name="selectionMode"> <cstring>files</cstring>
<enum>QAbstractItemView::ExtendedSelection</enum> </property>
</property> </widget>
<property name="sortingEnabled"> </item>
<bool>true</bool> <item>
</property> <widget class="QTreeView" name="files">
<attribute name="headerStretchLastSection"> <property name="alternatingRowColors">
<bool>false</bool> <bool>true</bool>
</attribute> </property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QCheckBox" name="optionalFiles">
<property name="text">
<string>&amp;Mark disabled files as optional</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item> <item>
@ -97,7 +124,8 @@
<tabstop>name</tabstop> <tabstop>name</tabstop>
<tabstop>version</tabstop> <tabstop>version</tabstop>
<tabstop>summary</tabstop> <tabstop>summary</tabstop>
<tabstop>treeView</tabstop> <tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections> <connections>

View File

@ -5,8 +5,6 @@
#include "ScrollMessageBox.h" #include "ScrollMessageBox.h"
#include "ui_ReviewMessageBox.h" #include "ui_ReviewMessageBox.h"
#include "FileSystem.h"
#include "Json.h"
#include "Markdown.h" #include "Markdown.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
@ -30,9 +28,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
} }
static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst) static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
{ {
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() }; return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
} }
ModUpdateDialog::ModUpdateDialog(QWidget* parent, ModUpdateDialog::ModUpdateDialog(QWidget* parent,

View File

@ -279,7 +279,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
{ {
QList<BasePage*> pages; QList<BasePage*> pages;
auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value(); auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().value();
if (ModrinthAPI::validateModLoaders(loaders)) if (ModrinthAPI::validateModLoaders(loaders))
pages.append(ModrinthModPage::create(this, *m_instance)); pages.append(ModrinthModPage::create(this, *m_instance));

View File

@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{ {
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value(); unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName; QString iconName;
@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) { } else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow"; iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
} else if (maxMem < minMem) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else { } else {
iconName = "status-good"; iconName = "status-good";
ui->labelMaxMemIcon->setToolTip(""); ui->labelMaxMemIcon->setToolTip("");

View File

@ -114,6 +114,7 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked()); s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked()); s->set("RecordGameTime", ui->recordGameTime->isChecked());
s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
// Miscellaneous // Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
@ -165,6 +166,7 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());

View File

@ -138,6 +138,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="showGameTimeWithoutDays">
<property name="text">
<string>Show time spent playing in hours</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -478,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{ {
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value(); unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName; QString iconName;
@ -487,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) { } else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow"; iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
} else if (maxMem < minMem) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else { } else {
iconName = "status-good"; iconName = "status-good";
ui->labelMaxMemIcon->setToolTip(""); ui->labelMaxMemIcon->setToolTip("");

View File

@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
} }
} }
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); }
{ int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }
return parent.isValid() ? 0 : m_servers.size();
}
int columnCount(const QModelIndex& parent) const override
{
return parent.isValid() ? 0 : COLUMN_COUNT;
}
Server* at(int index) Server* at(int index)
{ {
@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path; qDebug() << "Changed:" << path;
load(); load();
} }
void fileChanged(const QString& path) void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
{
qDebug() << "Changed:" << path;
}
private slots: private slots:
void save_internal() void save_internal()
@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop(); m_saveTimer.stop();
} }
bool saveIsScheduled() const bool saveIsScheduled() const { return m_dirty; }
{
return m_dirty;
}
void updateFSObserver() void updateFSObserver()
{ {

View File

@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
auto sort = getCurrentSortingMethodByIndex(); auto sort = getCurrentSortingMethodByIndex();
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions }; return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
} }
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty()) if (!m_filter->versions.empty())
versions = m_filter->versions; versions = m_filter->versions;
return { pack, versions, profile->getModLoaders() }; return { pack, versions, profile->getSupportedModLoaders() };
} }
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)

View File

@ -124,8 +124,7 @@ void ModPage::updateVersionList()
auto version = current_pack->versions[i]; auto version = current_pack->versions[i];
bool valid = false; bool valid = false;
for (auto& mcVer : m_filter->versions) { for (auto& mcVer : m_filter->versions) {
// NOTE: Flame doesn't care about loader, so passing it changes nothing. if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
valid = true; valid = true;
break; break;
} }

View File

@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer, QString mineVer,
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0; std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
[[nodiscard]] bool supportsFiltering() const override { return true; }; [[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }

View File

@ -132,6 +132,32 @@ void ResourceModel::search()
if (hasActiveSearchJob()) if (hasActiveSearchJob())
return; return;
if (m_search_term.startsWith("#")) {
auto projectId = m_search_term.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::ProjectInfoCallbacks callbacks;
callbacks.on_fail = [this](QString reason) {
if (!s_running_models.constFind(this).value())
return;
searchRequestFailed(reason, -1);
};
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
return;
searchRequestAborted();
};
callbacks.on_succeed = [this](auto& doc, auto& pack) {
if (!s_running_models.constFind(this).value())
return;
searchRequestForOneSucceeded(doc);
};
if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
runSearchJob(job);
return;
}
}
auto args{ createSearchArguments() }; auto args{ createSearchArguments() };
auto callbacks{ createSearchCallbacks() }; auto callbacks{ createSearchCallbacks() };
@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
// Use default if no callbacks are set // Use default if no callbacks are set
if (!callbacks.on_succeed) if (!callbacks.on_succeed)
callbacks.on_succeed = [this, entry](auto& doc, auto pack) { callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
if (!s_running_models.constFind(this).value()) if (!s_running_models.constFind(this).value())
return; return;
auto pack = newpack;
infoRequestSucceeded(doc, pack, entry); infoRequestSucceeded(doc, pack, entry);
}; };
if (!callbacks.on_fail)
callbacks.on_fail = [this](QString reason) {
if (!s_running_models.constFind(this).value())
return;
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
};
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job); runInfoJob(job);
@ -372,6 +405,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
endInsertRows(); endInsertRows();
} }
void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
{
ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
try {
auto obj = Json::requireObject(doc);
if (obj.contains("data"))
obj = Json::requireObject(obj, "data");
loadIndexedPack(*pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
}
m_search_state = SearchState::Finished;
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
m_packs.append(pack);
endInsertRows();
}
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
{ {
switch (network_error_code) { switch (network_error_code) {

View File

@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
private: private:
/* Default search request callbacks */ /* Default search request callbacks */
void searchRequestSucceeded(QJsonDocument&); void searchRequestSucceeded(QJsonDocument&);
void searchRequestForOneSucceeded(QJsonDocument&);
void searchRequestFailed(QString reason, int network_error_code); void searchRequestFailed(QString reason, int network_error_code);
void searchRequestAborted(); void searchRequestAborted();

View File

@ -44,9 +44,6 @@
#include <QKeyEvent> #include <QKeyEvent>
#include "Markdown.h" #include "Markdown.h"
#include "ResourceDownloadTask.h"
#include "minecraft/MinecraftInstance.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h" #include "ui/pages/modplatform/ResourceModel.h"

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h" #include "ShaderPackPage.h"
#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h" #include "ui_ResourcePage.h"
#include "ShaderPackModel.h" #include "ShaderPackModel.h"
@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model) const std::shared_ptr<ResourceFolderModel> base_model)
{ {
QString custom_target_folder; QString custom_target_folder;
if (version.loaders.contains(QStringLiteral("canvas"))) if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks"); custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder); m_model->addPack(pack, version, base_model, false, custom_target_folder);
} }

View File

@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
if (searchTerm.isEmpty()) { if (searchTerm.isEmpty()) {
return true; return true;
} }
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>(); ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
if (searchTerm.startsWith("#"))
return QString::number(pack.id) == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive); return pack.name.contains(searchTerm, Qt::CaseInsensitive);
} }

View File

@ -21,6 +21,7 @@
#include <Json.h> #include <Json.h>
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "ui/widgets/ProjectItem.h"
namespace Atl { namespace Atl {
@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
} }
ATLauncher::IndexedPack pack = modpacks.at(pos); ATLauncher::IndexedPack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) { switch (role) {
return pack.name; case Qt::ToolTipRole: {
} else if (role == Qt::ToolTipRole) { if (pack.description.length() > 100) {
return pack.name; // some magic to prevent to long tooltips and replace html linebreaks
} else if (role == Qt::DecorationRole) { QString edit = pack.description.left(97);
if (m_logoMap.contains(pack.safeName)) { edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return (m_logoMap.value(pack.safeName)); return edit;
}
return pack.description;
} }
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder"); case Qt::DecorationRole: {
if (m_logoMap.contains(pack.safeName)) {
return (m_logoMap.value(pack.safeName));
}
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName);
((ListModel*)this)->requestLogo(pack.safeName, url); ((ListModel*)this)->requestLogo(pack.safeName, url);
return icon; return icon;
} else if (role == Qt::UserRole) { }
QVariant v; case Qt::UserRole: {
v.setValue(pack); QVariant v;
return v; v.setValue(pack);
return v;
}
case Qt::DisplayRole:
return pack.name;
case Qt::SizeHintRole:
return QSize(0, 58);
// Custom data
case UserDataTypes::TITLE:
return pack.name;
case UserDataTypes::DESCRIPTION:
return pack.description;
case UserDataTypes::SELECTED:
return false;
case UserDataTypes::INSTALLED:
return false;
default:
break;
} }
return QVariant(); return {};
} }
void ListModel::request() void ListModel::request()

View File

@ -35,11 +35,11 @@
*/ */
#include "AtlPage.h" #include "AtlPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h" #include "ui_AtlPage.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h" #include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h" #include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged); connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
} }
AtlPage::~AtlPage() AtlPage::~AtlPage()

View File

@ -11,44 +11,7 @@
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2"> <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QTreeView" name="packView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>96</width>
<height>48</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
<item row="0" column="2"> <item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/> <widget class="QComboBox" name="versionSelectionBox"/>
@ -68,7 +31,34 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="0" column="0"> <item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeView" name="packView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>96</width>
<height>48</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="searchEdit"> <widget class="QLineEdit" name="searchEdit">
<property name="placeholderText"> <property name="placeholderText">
<string>Search and filter...</string> <string>Search and filter...</string>
@ -78,6 +68,31 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>

View File

@ -1,6 +1,8 @@
#include "FlameModel.h" #include "FlameModel.h"
#include <Json.h> #include <Json.h>
#include "Application.h" #include "Application.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch() void ListModel::performPaginatedSearch()
{ {
if (currentSearchTerm.startsWith("#")) {
auto projectId = currentSearchTerm.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::ProjectInfoCallbacks callbacks;
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
static const FlameAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;
jobPtr->start();
}
return;
}
}
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network()); auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString( auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?" "https://api.curseforge.com/v1/mods/search?"
@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
} }
currentSearchTerm = term; currentSearchTerm = term;
currentSort = sort; currentSort = sort;
if (jobPtr) { if (hasActiveSearchJob()) {
jobPtr->abort(); jobPtr->abort();
searchState = ResetRequested; searchState = ResetRequested;
return; return;
} else {
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
} }
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
nextSearchOffset = 0; nextSearchOffset = 0;
performPaginatedSearch(); performPaginatedSearch();
} }
void Flame::ListModel::searchRequestFinished() void Flame::ListModel::searchRequestFinished()
{ {
jobPtr.reset(); if (hasActiveSearchJob())
return;
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
endInsertRows(); endInsertRows();
} }
void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
{
jobPtr.reset();
auto packObj = Json::ensureObject(doc.object(), "data");
Flame::IndexedPack pack;
try {
Flame::loadIndexedPack(pack, packObj);
} catch (const JSONValidationError& e) {
qWarning() << "Error while loading pack from CurseForge: " << e.cause();
return;
}
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
modpacks.append({ pack });
endInsertRows();
}
void Flame::ListModel::searchRequestFailed(QString reason) void Flame::ListModel::searchRequestFailed(QString reason)
{ {
jobPtr.reset(); jobPtr.reset();

View File

@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort); void searchWithTerm(const QString& term, const int sort);
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
private slots: private slots:
void performPaginatedSearch(); void performPaginatedSearch();
@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished(); void searchRequestFinished();
void searchRequestFailed(QString reason); void searchRequestFailed(QString reason);
void searchRequestForOneSucceeded(QJsonDocument&);
private: private:
void requestLogo(QString file, QString url); void requestLogo(QString file, QString url);
@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0; int currentSort = 0;
int nextSearchOffset = 0; int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
NetJob::Ptr jobPtr; Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
}; };

View File

@ -50,7 +50,8 @@
static FlameAPI api; static FlameAPI api;
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
: QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch); connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
@ -61,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
m_search_timer.setSingleShot(true);
connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
m_fetch_progress.hideIfInactive(true);
m_fetch_progress.setFixedHeight(24);
m_fetch_progress.progressFormat("");
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
// index is used to set the sorting with the curseforge api // index is used to set the sorting with the curseforge api
ui->sortByBox->addItem(tr("Sort by Featured")); ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity")); ui->sortByBox->addItem(tr("Sort by Popularity"));
@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch(); triggerSearch();
keyEvent->accept(); keyEvent->accept();
return true; return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
} }
} }
return QWidget::eventFilter(watched, event); return QWidget::eventFilter(watched, event);
@ -114,6 +131,7 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch() void FlamePage::triggerSearch()
{ {
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
m_fetch_progress.watch(listModel->activeSearchJob().get());
} }
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)

View File

@ -39,8 +39,9 @@
#include <Application.h> #include <Application.h>
#include <modplatform/flame/FlamePackIndex.h> #include <modplatform/flame/FlamePackIndex.h>
#include "tasks/Task.h" #include <QTimer>
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui { namespace Ui {
class FlamePage; class FlamePage;
@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current; Flame::IndexedPack current;
int m_selected_version_index = -1; int m_selected_version_index = -1;
ProgressWidget m_fetch_progress;
// Used to do instant searching with a delay to cache quick changes
QTimer m_search_timer;
}; };

View File

@ -47,7 +47,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0"> <item row="3" column="0">
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QListView" name="packView"> <widget class="QListView" name="packView">
@ -77,7 +77,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QComboBox" name="sortByBox"/> <widget class="QComboBox" name="sortByBox"/>

View File

@ -31,7 +31,7 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{ {
return FlameMod::loadDependencyVersions(m, arr); return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
} }
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray

View File

@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer, QString mineVer,
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{ {
Q_UNUSED(loaders); return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
} }
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const

View File

@ -95,7 +95,7 @@ class FlameModPage : public ModPage {
bool validateVersion(ModPlatform::IndexedVersion& ver, bool validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer, QString mineVer,
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override; std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override; bool optedOut(ModPlatform::IndexedVersion& ver) const override;
void openUrl(const QUrl& url) override; void openUrl(const QUrl& url) override;

View File

@ -17,6 +17,7 @@
*/ */
#include "ImportFTBPage.h" #include "ImportFTBPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h" #include "ui_ImportFTBPage.h"
#include <QWidget> #include <QWidget>
@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this); ui->setupUi(this);
{ {
currentModel = new FilterModel(this);
listModel = new ListModel(this); listModel = new ListModel(this);
currentModel->setSourceModel(listModel);
ui->modpackList->setModel(listModel); ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true); ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide(); ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0); ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42)); ui->modpackList->setIconSize(QSize(42, 42));
for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
}
ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
} }
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged); connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset(); ui->modpackList->selectionModel()->reset();
} }
@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged(); onPackSelectionChanged();
return; return;
} }
Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>(); Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack); onPackSelectionChanged(&selectedPack);
} }
@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
dialog->setSuggestedPack(); dialog->setSuggestedPack();
} }
void ImportFTBPage::onSortingSelectionChanged(QString sort)
{
FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
currentModel->setSorting(toSet);
}
void ImportFTBPage::triggerSearch()
{
currentModel->setSearchTerm(ui->searchEdit->text());
}
} // namespace FTBImportAPP } // namespace FTBImportAPP

View File

@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
void suggestCurrent(); void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr); void onPackSelectionChanged(Modpack* pack = nullptr);
private slots: private slots:
void onSortingSelectionChanged(QString data);
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
void triggerSearch();
private: private:
bool initialized = false; bool initialized = false;
Modpack selected; Modpack selected;
ListModel* listModel = nullptr; ListModel* listModel = nullptr;
FilterModel* currentModel = nullptr;
NewInstanceDialog* dialog = nullptr; NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr; Ui::ImportFTBPage* ui = nullptr;

View File

@ -10,8 +10,8 @@
<height>1011</height> <height>1011</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="1" column="1">
<widget class="QTreeView" name="modpackList"> <widget class="QTreeView" name="modpackList">
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
@ -21,6 +21,54 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="sortByBox">
<property name="minimumSize">
<size>
<width>265</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -23,7 +23,9 @@
#include <QIcon> #include <QIcon>
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include "FileSystem.h" #include "FileSystem.h"
#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h" #include "modplatform/import_ftb/PackHelpers.h"
#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP { namespace FTBImportAPP {
@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
} }
auto pack = modpacks.at(pos); auto pack = modpacks.at(pos);
if (role == Qt::DisplayRole) { if (role == Qt::ToolTipRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
return pack.icon;
} else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
} else if (role == Qt::ToolTipRole) {
return tr("Minecraft %1").arg(pack.mcVersion);
} }
return QVariant(); switch (role) {
case Qt::ToolTipRole:
return tr("Minecraft %1").arg(pack.mcVersion);
case Qt::DecorationRole:
return pack.icon;
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
}
case Qt::DisplayRole:
return pack.name;
case Qt::SizeHintRole:
return QSize(0, 58);
// Custom data
case UserDataTypes::TITLE:
return pack.name;
case UserDataTypes::DESCRIPTION:
return tr("Minecraft %1").arg(pack.mcVersion);
case UserDataTypes::SELECTED:
return false;
case UserDataTypes::INSTALLED:
return false;
default:
break;
}
return {};
}
FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
{
currentSorting = Sorting::ByGameVersion;
sortings.insert(tr("Sort by Name"), Sorting::ByName);
sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
}
bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
if (currentSorting == Sorting::ByGameVersion) {
Version lv(leftPack.mcVersion);
Version rv(rightPack.mcVersion);
return lv < rv;
} else if (currentSorting == Sorting::ByName) {
return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
}
// UHM, some inavlid value set?!
qWarning() << "Invalid sorting set!";
return true;
}
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{
if (searchTerm.isEmpty()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
void FilterModel::setSearchTerm(const QString term)
{
searchTerm = term.trimmed();
invalidate();
}
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
{
return sortings;
}
QString FilterModel::translateCurrentSorting()
{
return sortings.key(currentSorting);
}
void FilterModel::setSorting(Sorting s)
{
currentSorting = s;
invalidate();
}
FilterModel::Sorting FilterModel::getCurrentSorting()
{
return currentSorting;
} }
} // namespace FTBImportAPP } // namespace FTBImportAPP

View File

@ -20,11 +20,33 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QIcon> #include <QIcon>
#include <QSortFilterProxyModel>
#include <QVariant> #include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h" #include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP { namespace FTBImportAPP {
class FilterModel : public QSortFilterProxyModel {
Q_OBJECT
public:
FilterModel(QObject* parent = Q_NULLPTR);
enum Sorting { ByName, ByGameVersion };
const QMap<QString, Sorting> getAvailableSortings();
QString translateCurrentSorting();
void setSorting(Sorting sorting);
Sorting getCurrentSorting();
void setSearchTerm(QString term);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
private:
QMap<QString, Sorting> sortings;
Sorting currentSorting;
QString searchTerm;
};
class ListModel : public QAbstractListModel { class ListModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT

View File

@ -41,6 +41,7 @@
#include <Version.h> #include <Version.h>
#include "StringUtils.h" #include "StringUtils.h"
#include "ui/widgets/ProjectItem.h"
#include <QLabel> #include <QLabel>
#include <QtMath> #include <QtMath>
@ -79,7 +80,20 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{ {
return true; if (searchTerm.isEmpty()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
if (searchTerm.startsWith("#"))
return pack.packCode == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
void FilterModel::setSearchTerm(const QString term)
{
searchTerm = term.trimmed();
invalidate();
} }
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
@ -139,39 +153,57 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
} }
Modpack pack = modpacks.at(pos); Modpack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) { switch (role) {
return pack.name + "\n" + translatePackType(pack.type); case Qt::ToolTipRole: {
} else if (role == Qt::ToolTipRole) { if (pack.description.length() > 100) {
if (pack.description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks
// some magic to prevent to long tooltips and replace html linebreaks QString edit = pack.description.left(97);
QString edit = pack.description.left(97); edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); return edit;
return edit; }
return pack.description;
} }
return pack.description; case Qt::DecorationRole: {
} else if (role == Qt::DecorationRole) { if (m_logoMap.contains(pack.logo)) {
if (m_logoMap.contains(pack.logo)) { return (m_logoMap.value(pack.logo));
return (m_logoMap.value(pack.logo)); }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel*)this)->requestLogo(pack.logo);
return icon;
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); case Qt::UserRole: {
((ListModel*)this)->requestLogo(pack.logo); QVariant v;
return icon; v.setValue(pack);
} else if (role == Qt::ForegroundRole) { return v;
if (pack.broken) {
// FIXME: Hardcoded color
return QColor(255, 0, 50);
} else if (pack.bugged) {
// FIXME: Hardcoded color
// bugged pack, currently only indicates bugged xml
return QColor(244, 229, 66);
} }
} else if (role == Qt::UserRole) { case Qt::ForegroundRole: {
QVariant v; if (pack.broken) {
v.setValue(pack); // FIXME: Hardcoded color
return v; return QColor(255, 0, 50);
} else if (pack.bugged) {
// FIXME: Hardcoded color
// bugged pack, currently only indicates bugged xml
return QColor(244, 229, 66);
}
}
case Qt::DisplayRole:
return pack.name;
case Qt::SizeHintRole:
return QSize(0, 58);
// Custom data
case UserDataTypes::TITLE:
return pack.name;
case UserDataTypes::DESCRIPTION:
return pack.description;
case UserDataTypes::SELECTED:
return false;
case UserDataTypes::INSTALLED:
return false;
default:
break;
} }
return QVariant(); return {};
} }
void ListModel::fill(ModpackList modpacks_) void ListModel::fill(ModpackList modpacks_)

View File

@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
QString translateCurrentSorting(); QString translateCurrentSorting();
void setSorting(Sorting sorting); void setSorting(Sorting sorting);
Sorting getCurrentSorting(); Sorting getCurrentSorting();
void setSearchTerm(QString term);
protected: protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
private: private:
QMap<QString, Sorting> sortings; QMap<QString, Sorting> sortings;
Sorting currentSorting; Sorting currentSorting;
QString searchTerm;
}; };
class ListModel : public QAbstractListModel { class ListModel : public QAbstractListModel {

View File

@ -35,6 +35,7 @@
*/ */
#include "Page.h" #include "Page.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h" #include "ui_Page.h"
#include <QInputDialog> #include <QInputDialog>
@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged); connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged); connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
ui->thirdPartyPackList->selectionModel()->reset(); ui->thirdPartyPackList->selectionModel()->reset();
ui->privatePackList->selectionModel()->reset(); ui->privatePackList->selectionModel()->reset();
ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
onTabChanged(ui->tabWidget->currentIndex()); onTabChanged(ui->tabWidget->currentIndex());
} }
@ -315,6 +321,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription; currentModpackInfo = ui->publicPackDescription;
} }
triggerSearch();
currentList->selectionModel()->reset(); currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex(); QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) { if (idx.isValid()) {
@ -354,4 +362,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged(); onPackSelectionChanged();
} }
void Page::triggerSearch()
{
currentModel->setSearchTerm(ui->searchEdit->text());
}
} // namespace LegacyFTB } // namespace LegacyFTB

View File

@ -43,7 +43,6 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "modplatform/legacy_ftb/PackFetchTask.h" #include "modplatform/legacy_ftb/PackFetchTask.h"
#include "modplatform/legacy_ftb/PackHelpers.h" #include "modplatform/legacy_ftb/PackHelpers.h"
#include "tasks/Task.h"
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
class NewInstanceDialog; class NewInstanceDialog;
@ -56,8 +55,6 @@ class Page;
class ListModel; class ListModel;
class FilterModel; class FilterModel;
class PrivatePackListModel;
class PrivatePackFilterModel;
class PrivatePackManager; class PrivatePackManager;
class Page : public QWidget, public BasePage { class Page : public QWidget, public BasePage {
@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
void onAddPackClicked(); void onAddPackClicked();
void onRemovePackClicked(); void onRemovePackClicked();
void triggerSearch();
private: private:
FilterModel* currentModel = nullptr; FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr; QTreeView* currentList = nullptr;

View File

@ -10,8 +10,29 @@
<height>602</height> <height>602</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout_5">
<item> <item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
@ -36,9 +57,9 @@
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription"> <widget class="QTextBrowser" name="publicPackDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -50,10 +71,10 @@
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription"> <widget class="QTextBrowser" name="thirdPartyPackDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList"> <widget class="QTreeView" name="thirdPartyPackList">
@ -104,16 +125,16 @@
</item> </item>
<item row="0" column="1" rowspan="3"> <item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription"> <widget class="QTextBrowser" name="privatePackDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">

View File

@ -38,8 +38,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "minecraft/MinecraftInstance.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "minecraft/PackProfile.h" #include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
@ -130,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
void ModpackListModel::performPaginatedSearch() void ModpackListModel::performPaginatedSearch()
{ {
// TODO: Move to standalone API if (hasActiveSearchJob())
return;
if (currentSearchTerm.startsWith("#")) {
auto projectId = currentSearchTerm.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::ProjectInfoCallbacks callbacks;
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
static const ModrinthAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;
jobPtr->start();
}
return;
}
} // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network()); auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?" "/search?"
@ -167,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh() void ModpackListModel::refresh()
{ {
if (jobPtr) { if (hasActiveSearchJob()) {
jobPtr->abort(); jobPtr->abort();
searchState = ResetRequested; searchState = ResetRequested;
return; return;
} else {
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
} }
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
nextSearchOffset = 0; nextSearchOffset = 0;
performPaginatedSearch(); performPaginatedSearch();
} }
@ -307,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
endInsertRows(); endInsertRows();
} }
void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
{
jobPtr.reset();
auto packObj = doc.object();
Modrinth::Modpack pack;
try {
Modrinth::loadIndexedPack(pack, packObj);
pack.id = Json::ensureString(packObj, "id", pack.id);
} catch (const JSONValidationError& e) {
qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
return;
}
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
modpacks.append({ pack });
endInsertRows();
}
void ModpackListModel::searchRequestFailed(QString reason) void ModpackListModel::searchRequestFailed(QString reason)
{ {
auto failed_action = jobPtr->getFailedActions().at(0); auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) { if (!failed_action->m_reply) {
// Network error // Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));

View File

@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
void refresh(); void refresh();
void searchWithTerm(const QString& term, const int sort); void searchWithTerm(const QString& term, const int sort);
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots: public slots:
void searchRequestFinished(QJsonDocument& doc_all); void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason); void searchRequestFailed(QString reason);
void searchRequestForOneSucceeded(QJsonDocument&);
protected slots: protected slots:
@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0; int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
NetJob::Ptr jobPtr; Task::Ptr jobPtr;
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>(); std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response; QByteArray m_specific_response;

View File

@ -52,7 +52,8 @@
#include <QKeyEvent> #include <QKeyEvent>
#include <QPushButton> #include <QPushButton>
ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog) ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
: QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -64,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
m_search_timer.setSingleShot(true);
connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
m_fetch_progress.hideIfInactive(true);
m_fetch_progress.setFixedHeight(24);
m_fetch_progress.progressFormat("");
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
ui->sortByBox->addItem(tr("Sort by Relevance")); ui->sortByBox->addItem(tr("Sort by Relevance"));
ui->sortByBox->addItem(tr("Sort by Total Downloads")); ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows")); ui->sortByBox->addItem(tr("Sort by Follows"));
@ -102,6 +114,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch(); this->triggerSearch();
keyEvent->accept(); keyEvent->accept();
return true; return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
} }
} }
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
@ -309,6 +326,7 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch() void ModrinthPage::triggerSearch()
{ {
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
m_fetch_progress.watch(m_model->activeSearchJob().get());
} }
void ModrinthPage::onVersionSelectionChanged(QString version) void ModrinthPage::onVersionSelectionChanged(QString version)

View File

@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/modrinth/ModrinthPackManifest.h"
#include "ui/widgets/ProgressWidget.h"
#include <QTimer>
#include <QWidget> #include <QWidget>
namespace Ui { namespace Ui {
@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current; Modrinth::Modpack current;
QString selectedVersion; QString selectedVersion;
ProgressWidget m_fetch_progress;
// Used to do instant searching with a delay to cache quick changes
QTimer m_search_timer;
}; };

View File

@ -10,8 +10,8 @@
<height>600</height> <height>600</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="font"> <property name="font">
<font> <font>
@ -29,7 +29,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="1" column="0">
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLineEdit" name="searchEdit"> <widget class="QLineEdit" name="searchEdit">
@ -47,7 +47,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item> <item row="3" column="0">
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QListView" name="packView"> <widget class="QListView" name="packView">
@ -77,7 +77,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item> <item row="4" column="0">
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QComboBox" name="sortByBox"/> <widget class="QComboBox" name="sortByBox"/>

View File

@ -39,12 +39,12 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{ {
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
} }
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{ {
return ::Modrinth::loadDependencyVersions(m, arr); return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
} }
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{ {
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
} }
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{ {
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
} }
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{ {
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
} }
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray

View File

@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer, QString mineVer,
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{ {
auto loaderCompatible = !loaders.has_value(); return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
if (!loaderCompatible) {
auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value());
for (auto remoteLoader : ver.loaders) {
if (loaderStrings.contains(remoteLoader)) {
loaderCompatible = true;
break;
}
}
}
return ver.mcVersion.contains(mineVer) && loaderCompatible;
} }
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)

View File

@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
-> bool override; -> bool override;
}; };

View File

@ -39,6 +39,7 @@
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "ui/widgets/ProjectItem.h"
#include <QFileInfo> #include <QFileInfo>
#include <QIcon> #include <QIcon>
@ -56,21 +57,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
} }
Modpack pack = modpacks.at(pos); Modpack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) { switch (role) {
return pack.name; case Qt::ToolTipRole: {
} else if (role == Qt::DecorationRole) { if (pack.description.length() > 100) {
if (m_logoMap.contains(pack.logoName)) { // some magic to prevent to long tooltips and replace html linebreaks
return (m_logoMap.value(pack.logoName)); QString edit = pack.description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit;
}
return pack.description;
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); case Qt::DecorationRole: {
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); if (m_logoMap.contains(pack.logoName)) {
return icon; return (m_logoMap.value(pack.logoName));
} else if (role == Qt::UserRole) { }
QVariant v; QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
v.setValue(pack); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return v; return icon;
}
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
}
case Qt::DisplayRole:
return pack.name;
case Qt::SizeHintRole:
return QSize(0, 58);
// Custom data
case UserDataTypes::TITLE:
return pack.name;
case UserDataTypes::DESCRIPTION:
return pack.description;
case UserDataTypes::SELECTED:
return false;
case UserDataTypes::INSTALLED:
return false;
default:
break;
} }
return QVariant();
return {};
} }
int Technic::ListModel::columnCount(const QModelIndex& parent) const int Technic::ListModel::columnCount(const QModelIndex& parent) const
@ -89,21 +116,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return; return;
} }
currentSearchTerm = term; currentSearchTerm = term;
if (jobPtr) { if (hasActiveSearchJob()) {
jobPtr->abort(); jobPtr->abort();
searchState = ResetRequested; searchState = ResetRequested;
return; return;
} else {
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
} }
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
performSearch(); performSearch();
} }
void Technic::ListModel::performSearch() void Technic::ListModel::performSearch()
{ {
if (hasActiveSearchJob())
return;
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network()); auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = ""; QString searchUrl = "";
if (currentSearchTerm.isEmpty()) { if (currentSearchTerm.isEmpty()) {
@ -115,6 +146,9 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single; searchMode = Single;
} else if (currentSearchTerm.startsWith("#")) {
searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
} else { } else {
searchUrl = searchUrl =
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);

View File

@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term); void searchWithTerm(const QString& term);
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
private slots: private slots:
void searchRequestFinished(); void searchRequestFinished();
void searchRequestFailed(); void searchRequestFailed();

View File

@ -34,6 +34,7 @@
*/ */
#include "TechnicPage.h" #include "TechnicPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h" #include "ui_TechnicPage.h"
#include <QKeyEvent> #include <QKeyEvent>
@ -51,7 +52,8 @@
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch); connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
@ -59,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
model = new Technic::ListModel(this); model = new Technic::ListModel(this);
ui->packView->setModel(model); ui->packView->setModel(model);
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
m_search_timer.setSingleShot(true);
connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
m_fetch_progress.hideIfInactive(true);
m_fetch_progress.setFixedHeight(24);
m_fetch_progress.progressFormat("");
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
} }
bool TechnicPage::eventFilter(QObject* watched, QEvent* event) bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@ -71,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch(); triggerSearch();
keyEvent->accept(); keyEvent->accept();
return true; return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
} }
} }
return QWidget::eventFilter(watched, event); return QWidget::eventFilter(watched, event);
@ -100,6 +120,7 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch() void TechnicPage::triggerSearch()
{ {
model->searchWithTerm(ui->searchEdit->text()); model->searchWithTerm(ui->searchEdit->text());
m_fetch_progress.watch(model->activeSearchJob().get());
} }
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second) void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)

View File

@ -35,13 +35,14 @@
#pragma once #pragma once
#include <QTimer>
#include <QWidget> #include <QWidget>
#include <Application.h> #include <Application.h>
#include "TechnicData.h" #include "TechnicData.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/Task.h"
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui { namespace Ui {
class TechnicPage; class TechnicPage;
@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
ProgressWidget m_fetch_progress;
// Used to do instant searching with a delay to cache quick changes
QTimer m_search_timer;
}; };

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2"> <item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/> <widget class="QComboBox" name="versionSelectionBox"/>
@ -44,7 +44,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QListView" name="packView"> <widget class="QListView" name="packView">

View File

@ -186,12 +186,20 @@ QString JavaSettingsWidget::javaPath() const
int JavaSettingsWidget::maxHeapSize() const int JavaSettingsWidget::maxHeapSize() const
{ {
return m_maxMemSpinBox->value(); auto min = m_minMemSpinBox->value();
auto max = m_maxMemSpinBox->value();
if (max < min)
max = min;
return max;
} }
int JavaSettingsWidget::minHeapSize() const int JavaSettingsWidget::minHeapSize() const
{ {
return m_minMemSpinBox->value(); auto min = m_minMemSpinBox->value();
auto max = m_maxMemSpinBox->value();
if (min > max)
min = max;
return min;
} }
bool JavaSettingsWidget::permGenEnabled() const bool JavaSettingsWidget::permGenEnabled() const
@ -214,17 +222,9 @@ void JavaSettingsWidget::memoryValueChanged(int)
if (obj == m_minMemSpinBox && min != observedMinMemory) { if (obj == m_minMemSpinBox && min != observedMinMemory) {
observedMinMemory = min; observedMinMemory = min;
actuallyChanged = true; actuallyChanged = true;
if (min > max) {
observedMaxMemory = min;
m_maxMemSpinBox->setValue(min);
}
} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) { } else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
observedMaxMemory = max; observedMaxMemory = max;
actuallyChanged = true; actuallyChanged = true;
if (min > max) {
observedMinMemory = max;
m_minMemSpinBox->setValue(max);
}
} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) { } else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
observedPermGenMemory = permgen; observedPermGenMemory = permgen;
actuallyChanged = true; actuallyChanged = true;
@ -361,8 +361,8 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
setJavaStatus(JavaStatus::Pending); setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker()); m_checker.reset(new JavaChecker());
m_checker->m_path = path; m_checker->m_path = path;
m_checker->m_minMem = m_minMemSpinBox->value(); m_checker->m_minMem = minHeapSize();
m_checker->m_maxMem = m_maxMemSpinBox->value(); m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) { if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value(); m_checker->m_permGen = m_permGenSpinBox->value();
} }
@ -415,6 +415,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory > (m_availableMemory * 0.9)) { } else if (observedMaxMemory > (m_availableMemory * 0.9)) {
iconName = "status-yellow"; iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
} else if (observedMaxMemory < observedMinMemory) {
iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else { } else {
iconName = "status-good"; iconName = "status-good";
m_labelMaxMemIcon->setToolTip(""); m_labelMaxMemIcon->setToolTip("");

View File

@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width(); icon_width = icon_size.width();
icon_height = icon_size.height(); icon_height = icon_size.height();
icon_x_margin = (rect.height() - icon_width) / 2;
icon_y_margin = (rect.height() - icon_height) / 2; icon_y_margin = (rect.height() - icon_height) / 2;
icon_x_margin = icon_y_margin; // use same margins for consistency
} }
// Centralize icon with a margin to separate from the other elements // Centralize icon with a margin to separate from the other elements

View File

@ -220,5 +220,5 @@ void LocalPeer::receiveConnection()
socket->waitForBytesWritten(1000); socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack socket->waitForDisconnected(1000); // make sure client reads ack
delete socket; delete socket;
emit messageReceived(uMsg); //### (might take a long time to return) emit messageReceived(uMsg); // ### (might take a long time to return)
} }

View File

@ -23,18 +23,13 @@
types_or = ["c" "c++" "java" "json" "objective-c"]; types_or = ["c" "c++" "java" "json" "objective-c"];
}; };
}; };
tools.clang-tools = pkgs.clang-tools_16;
}; };
}; };
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
inherit (self.checks.${system}.pre-commit-check) shellHook; inherit (self.checks.${system}.pre-commit-check) shellHook;
packages = with pkgs; [
nodePackages.markdownlint-cli
alejandra
deadnix
clang-tools
nil
];
inputsFrom = [self.packages.${system}.prismlauncher-unwrapped]; inputsFrom = [self.packages.${system}.prismlauncher-unwrapped];
buildInputs = with pkgs; [ccache ninja]; buildInputs = with pkgs; [ccache ninja];

View File

@ -1,6 +1,7 @@
{ {
lib, lib,
stdenv, stdenv,
canonicalize-jars-hook,
cmake, cmake,
cmark, cmark,
Cocoa, Cocoa,
@ -26,7 +27,7 @@ assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is on
src = lib.cleanSource self; src = lib.cleanSource self;
nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja]; nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja canonicalize-jars-hook];
buildInputs = buildInputs =
[ [
qtbase qtbase

Some files were not shown because too many files have changed in this diff Show More