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
watch_file nix/*.nix

View File

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

6
flake.lock generated
View File

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

View File

@ -594,6 +594,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods
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
{
auto fileName = fileInfo.fileName();
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);
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const

View File

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

View File

@ -20,7 +20,7 @@
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`.

View File

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

View File

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

View File

@ -195,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false);
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!";
setSpecificSettingsLoaded(true);
@ -934,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(tr(", last played on %1 for %2")
description.append(
tr(", last played on %1 for %2")
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed())));
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
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()) {

View File

@ -58,15 +58,14 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
#include "Application.h"
#include "modplatform/ResourceAPI.h"
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge },
{ "net.minecraftforge", ResourceAPI::Forge },
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
{ "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
{ "org.quiltmc.quilt-loader", ModPlatform::Quilt },
{ "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
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;
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) {
i.next();
@ -1009,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {};
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 <QString>
#include <memory>
#include <optional>
#include "BaseVersion.h"
#include "Component.h"
#include "LaunchProfile.h"
#include "Library.h"
#include "MojangDownloadInfo.h"
#include "ProfileUtils.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/ModIndex.h"
#include "net/Mode.h"
class MinecraftInstance;
@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
// todo(merged): is this the best approach
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:
void scheduleSave();

View File

@ -51,8 +51,13 @@
#include "Application.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.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)
: 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));
}
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/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
@ -75,6 +76,7 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override;
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);
/// 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();
}
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,
@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
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 over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
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);
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 over = std::find_if(overide.cbegin(), overide.cend(),
[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;
}
}
removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;

View File

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

View File

@ -14,7 +14,7 @@ class CheckUpdateTask : public Task {
public:
CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> 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:
QList<Mod*>& m_mods;
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::vector<UpdatableMod> m_updatable;

View File

@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
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

View File

@ -30,6 +30,9 @@ class QIODevice;
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 ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@ -70,7 +73,7 @@ struct IndexedVersion {
QString downloadUrl;
QString date;
QString fileName;
QStringList loaders = {};
ModLoaderTypes loaders = {};
QString hash_type;
QString hash;
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; });
}
};
QString getMetaURL(ResourceProvider provider, QVariant projectID);
struct OverrideDep {
QString quilt;
@ -148,6 +150,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
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
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -54,9 +54,6 @@ class ResourceAPI {
public:
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 {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search;
std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders;
std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders;
std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
@ -108,13 +105,15 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
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 {
ModPlatform::Dependency dependency;
Version mcVersion;
ModLoaderTypes loader;
ModPlatform::ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
@ -161,27 +160,6 @@ class ResourceAPI {
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:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }

View File

@ -1,6 +1,7 @@
#include "FileResolvingTask.h"
#include "Json.h"
#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.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 output = std::make_shared<QByteArray>();
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);
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
// which file is relative to each loader, so it's best to not use any one and
// let the user download it manually.
if (file.loaders.size() <= 1) {
if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName;
} else {
@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::ApiDownload::makeByteArray(url, output);
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 json = QJsonDocument::fromJson(*output);
auto base =

View File

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

View File

@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI {
[[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:
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
if (loaders & Forge)
switch (loaders) {
case ModPlatform::Forge:
return 1;
if (loaders & Fabric)
case ModPlatform::Cauldron:
return 2;
case ModPlatform::LiteLoader:
return 3;
case ModPlatform::Fabric:
return 4;
// TODO: remove this once Quilt drops official Fabric support
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
return 4; // FIXME: implement multiple loaders filter (this should be 5)
// TODO: remove this once NeoForge drops official Forge support
if (loaders & NeoForge) // NOTE: Most if not all Forge mods should work *currently*
return 1; // FIXME: implement multiple loaders filter (this should be 6)
case ModPlatform::Quilt:
return 5;
case ModPlatform::NeoForge:
return 6;
}
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:
[[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("sortOrder=desc");
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);
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
{
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())
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()) {
int mappedModLoader = getMappedModLoader(args.loaders.value());
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;
if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
}
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
}
return url + get_parameters.join('&');
return url;
};
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString();
if (args.loader & 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;
auto url =
QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
}
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
.arg(addonId)
.arg(args.mcVersion.toString())
.arg(mappedModLoader);
return url;
};
};

View File

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

View File

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

View File

@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid())
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);
}
@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.'))
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");
@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
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;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid())
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);
}
@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
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 BaseInstance* inst);
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

View File

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

View File

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

View File

@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
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;
}
@ -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 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, [=] {
QJsonParseError parse_error{};

View File

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

View File

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

View File

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

View File

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

View File

@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
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; };
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
{
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) {
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;
}
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{
QStringList l;
for (auto loader : getModLoaderStrings(types)) {
@ -143,9 +140,9 @@ class ModrinthAPI : public NetworkResourceAPI {
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

View File

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

View File

@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public:
ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> 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,
const QString& version,
const QString& summary,
bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, summary(summary)
, optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
@ -270,6 +272,7 @@ QByteArray ModrinthPackExportTask::generateIndex()
QString path = iterator.key();
const ResolvedFile& value = iterator.value();
if (optionalFiles) {
// detect disabled mod
const QFileInfo pathInfo(path);
if (pathInfo.suffix() == "disabled") {
@ -281,6 +284,7 @@ QByteArray ModrinthPackExportTask::generateIndex()
env["server"] = "optional";
fileOut["env"] = env;
}
}
fileOut["path"] = path;
fileOut["downloads"] = QJsonArray{ iterator.value().url };

View File

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

View File

@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true;
}
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst)
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
{
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) {
auto obj = versionIter.toObject();
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);
}
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");
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_number = Json::requireString(obj, "version_number");
@ -218,15 +229,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
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) {
auto obj = versionIter.toObject();
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);
}
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 loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
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

View File

@ -43,7 +43,6 @@
#include "FileSystem.h"
#include "MainWindow.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h"
#include <QDir>
@ -90,17 +89,14 @@
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h>
#include "InstancePageProvider.h"
#include "InstanceWindow.h"
#include "JavaCommon.h"
#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h"
@ -113,17 +109,22 @@
#include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h"
#include "minecraft/PackProfile.h"
#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
#include "Json.h"
@ -924,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
ModPlatform::IndexedVersion version;
QMap<QString, QString> extra_info;
QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify
@ -949,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
auto api = FlameAPI();
auto job = api.getFile(addonId, fileId, array);
QString resource_name;
connect(job.get(), &Task::failed, this,
[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();
auto doc = Json::requireDocument(*array);
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
// 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
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
dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
dl_url = QUrl(version.downloadUrl);
if (!dl_url.isValid()) {
CustomMessageBox::selectable(
this, tr("Error"),
@ -973,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
}
QFileInfo dl_file(dl_url.fileName());
resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
});
{ // drop stack
@ -1048,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break;
case PackedResourceType::Mod:
minecraftInst->loaderModList()->installMod(localFileName);
minecraftInst->loaderModList()->installMod(localFileName, version);
break;
case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName);
@ -1735,7 +1735,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime();
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)

View File

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

View File

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

View File

@ -7,12 +7,9 @@
<x>0</x>
<y>0</y>
<width>650</width>
<height>413</height>
<height>510</height>
</rect>
</property>
<property name="windowTitle">
<string>Export Pack</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
@ -20,13 +17,16 @@
<item>
<widget class="QGroupBox" name="information">
<property name="title">
<string>Information</string>
<string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
<string>Summary</string>
<string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property>
</widget>
</item>
@ -36,14 +36,20 @@
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
<string>&amp;Name</string>
</property>
<property name="buddy">
<cstring>name</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>Version</string>
<string>&amp;Version</string>
</property>
<property name="buddy">
<cstring>version</cstring>
</property>
</widget>
</item>
@ -57,19 +63,27 @@
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="options">
<property name="title">
<string>&amp;Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="filesLabel">
<property name="text">
<string>Files</string>
<string>&amp;Files</string>
</property>
<property name="buddy">
<cstring>files</cstring>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="treeView">
<widget class="QTreeView" name="files">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@ -84,6 +98,19 @@
</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>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
@ -97,7 +124,8 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
<tabstop>treeView</tabstop>
<tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops>
<resources/>
<connections>

View File

@ -5,8 +5,6 @@
#include "ScrollMessageBox.h"
#include "ui_ReviewMessageBox.h"
#include "FileSystem.h"
#include "Json.h"
#include "Markdown.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() };
}
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,

View File

@ -279,7 +279,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
{
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))
pages.append(ModrinthModPage::create(this, *m_instance));

View File

@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
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 {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");

View File

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

View File

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

View File

@ -478,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@ -487,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
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 {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");

View File

@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
}
}
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;
}
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; }
Server* at(int index)
{
@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path;
load();
}
void fileChanged(const QString& path)
{
qDebug() << "Changed:" << path;
}
void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
private slots:
void save_internal()
@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop();
}
bool saveIsScheduled() const
{
return m_dirty;
}
bool saveIsScheduled() const { return m_dirty; }
void updateFSObserver()
{

View File

@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
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)
@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty())
versions = m_filter->versions;
return { pack, versions, profile->getModLoaders() };
return { pack, versions, profile->getSupportedModLoaders() };
}
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)

View File

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

View File

@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
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; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }

View File

@ -132,6 +132,32 @@ void ResourceModel::search()
if (hasActiveSearchJob())
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 callbacks{ createSearchCallbacks() };
@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
// Use default if no callbacks are set
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())
return;
auto pack = newpack;
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)
runInfoJob(job);
@ -372,6 +405,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
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)
{
switch (network_error_code) {

View File

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

View File

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

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h"
#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include "ShaderPackModel.h"
@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model)
{
QString custom_target_folder;
if (version.loaders.contains(QStringLiteral("canvas")))
if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks");
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()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
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);
}

View File

@ -21,6 +21,7 @@
#include <Json.h>
#include "net/ApiDownload.h"
#include "ui/widgets/ProjectItem.h"
namespace Atl {
@ -46,11 +47,17 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name;
} else if (role == Qt::ToolTipRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
switch (role) {
case Qt::ToolTipRole: {
if (pack.description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit;
}
return pack.description;
}
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.safeName)) {
return (m_logoMap.value(pack.safeName));
}
@ -60,13 +67,30 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
((ListModel*)this)->requestLogo(pack.safeName, url);
return icon;
} else if (role == Qt::UserRole) {
}
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 {};
}
void ListModel::request()

View File

@ -35,11 +35,11 @@
*/
#include "AtlPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h"
#include "BuildConfig.h"
#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.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->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
AtlPage::~AtlPage()

View File

@ -11,44 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" 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">
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@ -68,7 +31,34 @@
</item>
</layout>
</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">
<property name="placeholderText">
<string>Search and filter...</string>
@ -78,6 +68,31 @@
</property>
</widget>
</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>
</widget>
<tabstops>

View File

@ -1,6 +1,8 @@
#include "FlameModel.h"
#include <Json.h>
#include "Application.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
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 searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
}
currentSearchTerm = term;
currentSort = sort;
if (jobPtr) {
if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
} else {
}
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
}
nextSearchOffset = 0;
performPaginatedSearch();
}
void Flame::ListModel::searchRequestFinished()
{
jobPtr.reset();
if (hasActiveSearchJob())
return;
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
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)
{
jobPtr.reset();

View File

@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
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:
void performPaginatedSearch();
@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished();
void searchRequestFailed(QString reason);
void searchRequestForOneSucceeded(QJsonDocument&);
private:
void requestLogo(QString file, QString url);
@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
NetJob::Ptr jobPtr;
Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};

View File

@ -50,7 +50,8 @@
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);
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()->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
ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity"));
@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@ -114,6 +131,7 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch()
{
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
m_fetch_progress.watch(listModel->activeSearchJob().get());
}
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)

View File

@ -39,8 +39,9 @@
#include <Application.h>
#include <modplatform/flame/FlamePackIndex.h>
#include "tasks/Task.h"
#include <QTimer>
#include "ui/pages/BasePage.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class FlamePage;
@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current;
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>
</layout>
</item>
<item row="2" column="0">
<item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@ -77,7 +77,7 @@
</item>
</layout>
</item>
<item row="3" column="0">
<item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<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
{
return FlameMod::loadDependencyVersions(m, arr);
return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
}
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,
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

View File

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

View File

@ -17,6 +17,7 @@
*/
#include "ImportFTBPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this);
{
currentModel = new FilterModel(this);
listModel = new ListModel(this);
currentModel->setSourceModel(listModel);
ui->modpackList->setModel(listModel);
ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
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->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged();
return;
}
Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
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

View File

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

View File

@ -10,8 +10,8 @@
<height>1011</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@ -21,6 +21,54 @@
</property>
</widget>
</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>
</widget>
<resources/>

View File

@ -23,7 +23,9 @@
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP {
@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
if (role == Qt::ToolTipRole) {
}
switch (role) {
case Qt::ToolTipRole:
return tr("Minecraft %1").arg(pack.mcVersion);
case Qt::DecorationRole:
return pack.icon;
} else if (role == Qt::UserRole) {
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
} else if (role == Qt::ToolTipRole) {
}
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 QVariant();
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

View File

@ -20,11 +20,33 @@
#include <QAbstractListModel>
#include <QIcon>
#include <QSortFilterProxyModel>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
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 {
Q_OBJECT

View File

@ -41,6 +41,7 @@
#include <Version.h>
#include "StringUtils.h"
#include "ui/widgets/ProjectItem.h"
#include <QLabel>
#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
{
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()
@ -139,9 +153,8 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name + "\n" + translatePackType(pack.type);
} else if (role == Qt::ToolTipRole) {
switch (role) {
case Qt::ToolTipRole: {
if (pack.description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97);
@ -149,14 +162,21 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return edit;
}
return pack.description;
} else if (role == Qt::DecorationRole) {
}
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logo)) {
return (m_logoMap.value(pack.logo));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel*)this)->requestLogo(pack.logo);
return icon;
} else if (role == Qt::ForegroundRole) {
}
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
}
case Qt::ForegroundRole: {
if (pack.broken) {
// FIXME: Hardcoded color
return QColor(255, 0, 50);
@ -165,13 +185,25 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
// bugged pack, currently only indicates bugged xml
return QColor(244, 229, 66);
}
} else if (role == 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 {};
}
void ListModel::fill(ModpackList modpacks_)

View File

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

View File

@ -35,6 +35,7 @@
*/
#include "Page.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h"
#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->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->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
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->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());
}
@ -315,6 +321,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription;
}
triggerSearch();
currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) {
@ -354,4 +362,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged();
}
void Page::triggerSearch()
{
currentModel->setSearchTerm(ui->searchEdit->text());
}
} // namespace LegacyFTB

View File

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

View File

@ -10,8 +10,29 @@
<height>602</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QGridLayout" name="gridLayout_5">
<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">
<property name="currentIndex">
<number>0</number>
@ -113,7 +134,7 @@
</widget>
</widget>
</item>
<item>
<item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label">

View File

@ -38,8 +38,8 @@
#include "BuildConfig.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@ -130,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
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 searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
@ -167,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh()
{
if (jobPtr) {
if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
} else {
}
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
}
nextSearchOffset = 0;
performPaginatedSearch();
}
@ -307,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
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)
{
auto failed_action = jobPtr->getFailedActions().at(0);
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
// Network error
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 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);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason);
void searchRequestForOneSucceeded(QJsonDocument&);
protected slots:
@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0;
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>();
QByteArray m_specific_response;

View File

@ -52,7 +52,8 @@
#include <QKeyEvent>
#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);
@ -64,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
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 Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
@ -102,6 +114,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch();
keyEvent->accept();
return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
}
}
return QObject::eventFilter(watched, event);
@ -309,6 +326,7 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch()
{
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
m_fetch_progress.watch(m_model->activeSearchJob().get());
}
void ModrinthPage::onVersionSelectionChanged(QString version)

View File

@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
#include "ui/widgets/ProgressWidget.h"
#include <QTimer>
#include <QWidget>
namespace Ui {
@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current;
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>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
@ -29,7 +29,7 @@
</property>
</widget>
</item>
<item>
<item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
@ -47,7 +47,7 @@
</item>
</layout>
</item>
<item>
<item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@ -77,7 +77,7 @@
</item>
</layout>
</item>
<item>
<item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<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)
{
::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
{
return ::Modrinth::loadDependencyVersions(m, arr);
return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
}
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)
{
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
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)
{
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
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)
{
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
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,
QString mineVer,
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
auto loaderCompatible = !loaders.has_value();
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;
return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
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"; }
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;
};

View File

@ -39,6 +39,7 @@
#include "Json.h"
#include "net/ApiDownload.h"
#include "ui/widgets/ProjectItem.h"
#include <QFileInfo>
#include <QIcon>
@ -56,21 +57,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
switch (role) {
case Qt::ToolTipRole: {
if (pack.description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit;
}
return pack.description;
}
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) {
return (m_logoMap.value(pack.logoName));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon;
} else if (role == Qt::UserRole) {
}
case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
}
return QVariant();
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 {};
}
int Technic::ListModel::columnCount(const QModelIndex& parent) const
@ -89,21 +116,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return;
}
currentSearchTerm = term;
if (jobPtr) {
if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
} else {
}
beginResetModel();
modpacks.clear();
endResetModel();
searchState = None;
}
performSearch();
}
void Technic::ListModel::performSearch()
{
if (hasActiveSearchJob())
return;
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
@ -115,6 +146,9 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
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 {
searchUrl =
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 searchWithTerm(const QString& term);
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
private slots:
void searchRequestFinished();
void searchRequestFailed();

View File

@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
#include <QKeyEvent>
@ -51,7 +52,8 @@
#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);
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);
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->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@ -71,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
} else {
if (m_search_timer.isActive())
m_search_timer.stop();
m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@ -100,6 +120,7 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
m_fetch_progress.watch(model->activeSearchJob().get());
}
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)

View File

@ -35,13 +35,14 @@
#pragma once
#include <QTimer>
#include <QWidget>
#include <Application.h>
#include "TechnicData.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class TechnicPage;
@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr;
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>
</property>
<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">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@ -44,7 +44,7 @@
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView">

View File

@ -186,12 +186,20 @@ QString JavaSettingsWidget::javaPath() 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
{
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
@ -214,17 +222,9 @@ void JavaSettingsWidget::memoryValueChanged(int)
if (obj == m_minMemSpinBox && min != observedMinMemory) {
observedMinMemory = min;
actuallyChanged = true;
if (min > max) {
observedMaxMemory = min;
m_maxMemSpinBox->setValue(min);
}
} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
observedMaxMemory = max;
actuallyChanged = true;
if (min > max) {
observedMinMemory = max;
m_minMemSpinBox->setValue(max);
}
} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
observedPermGenMemory = permgen;
actuallyChanged = true;
@ -361,8 +361,8 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker());
m_checker->m_path = path;
m_checker->m_minMem = m_minMemSpinBox->value();
m_checker->m_maxMem = m_maxMemSpinBox->value();
m_checker->m_minMem = minHeapSize();
m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value();
}
@ -415,6 +415,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory > (m_availableMemory * 0.9)) {
iconName = "status-yellow";
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 {
iconName = "status-good";
m_labelMaxMemIcon->setToolTip("");

View File

@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width();
icon_height = icon_size.height();
icon_x_margin = (rect.height() - icon_width) / 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

View File

@ -220,5 +220,5 @@ void LocalPeer::receiveConnection()
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
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"];
};
};
tools.clang-tools = pkgs.clang-tools_16;
};
};
devShells.default = pkgs.mkShell {
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];
buildInputs = with pkgs; [ccache ninja];

View File

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

View File

@ -4,7 +4,7 @@ Name=Prism Launcher
Comment=A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
Type=Application
Terminal=false
Exec=@Launcher_APP_BINARY_NAME@
Exec=@Launcher_APP_BINARY_NAME@ %U
StartupNotify=true
Icon=org.prismlauncher.PrismLauncher
Categories=Game;ActionGame;AdventureGame;Simulation;

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