add curseforge modpack filter

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-10-02 01:06:45 +03:00
parent 44b4262f3c
commit dfe3cd849d
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
15 changed files with 242 additions and 75 deletions

View File

@ -35,7 +35,7 @@ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
enum class ResourceProvider { MODRINTH, FLAME }; enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK };
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };

View File

@ -221,14 +221,20 @@ QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } }; { 8, "GameVersion", QObject::tr("Sort by Game Version") } };
} }
Task::Ptr FlameAPI::getModCategories(std::shared_ptr<QByteArray> response) Task::Ptr FlameAPI::getCategories(std::shared_ptr<QByteArray> response, ModPlatform::ResourceType type)
{ {
auto netJob = makeShared<NetJob>(QString("Flame::GetCategories"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetCategories"), APPLICATION->network());
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl("https://api.curseforge.com/v1/categories?gameId=432&classId=6"), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(
QUrl(QString("https://api.curseforge.com/v1/categories?gameId=432&classId=%1").arg(getClassId(type))), response));
QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; }); QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; });
return netJob; return netJob;
} }
Task::Ptr FlameAPI::getModCategories(std::shared_ptr<QByteArray> response)
{
return getCategories(response, ModPlatform::ResourceType::MOD);
}
QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteArray> response) QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteArray> response)
{ {
QList<ModPlatform::Category> categories; QList<ModPlatform::Category> categories;

View File

@ -25,6 +25,7 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const; Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const; Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
static Task::Ptr getCategories(std::shared_ptr<QByteArray> response, ModPlatform::ResourceType type);
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response); static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response); static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
@ -46,6 +47,8 @@ class FlameAPI : public NetworkResourceAPI {
return 12; return 12;
case ModPlatform::ResourceType::SHADER_PACK: case ModPlatform::ResourceType::SHADER_PACK:
return 6552; return 6552;
case ModPlatform::ResourceType::MODPACK:
return 4471;
} }
} }
@ -82,8 +85,8 @@ class FlameAPI : public NetworkResourceAPI {
static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
private: public:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override static std::optional<QString> getStaticSearchURL(SearchArgs const& args)
{ {
auto gameVersionStr = auto gameVersionStr =
args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
@ -105,12 +108,14 @@ class FlameAPI : public NetworkResourceAPI {
get_arguments.append(gameVersionStr); get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
}; }
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override { return getStaticSearchURL(args); }
private:
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override [[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
{ {
return QString("https://api.curseforge.com/v1/mods/%1").arg(id); return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
}; }
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{ {
@ -125,7 +130,7 @@ class FlameAPI : public NetworkResourceAPI {
url += QString("&modLoaderType=%1").arg(mappedModLoader); url += QString("&modLoaderType=%1").arg(mappedModLoader);
} }
return url; return url;
}; }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{ {
@ -137,5 +142,5 @@ class FlameAPI : public NetworkResourceAPI {
url += QString("&modLoaderType=%1").arg(mappedModLoader); url += QString("&modLoaderType=%1").arg(mappedModLoader);
} }
return url; return url;
}; }
}; };

View File

@ -3,6 +3,7 @@
#include <QUrl> #include <QUrl>
#include "Json.h" #include "Json.h"
#include "modplatform/ModIndex.h"
void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
{ {
@ -88,8 +89,27 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
continue; continue;
} }
for (auto mcVer : versionArray) {
auto str = mcVer.toString();
if (str.contains('.'))
file.mcVersion.append(str);
if (auto loader = str.toLower(); loader == "neoforge")
file.loaders |= ModPlatform::NeoForge;
else if (loader == "forge")
file.loaders |= ModPlatform::Forge;
else if (loader == "cauldron")
file.loaders |= ModPlatform::Cauldron;
else if (loader == "liteloader")
file.loaders |= ModPlatform::LiteLoader;
else if (loader == "fabric")
file.loaders |= ModPlatform::Fabric;
else if (loader == "quilt")
file.loaders |= ModPlatform::Quilt;
}
// pick the latest version supported // pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName"); file.version = Json::requireString(version, "displayName");
ModPlatform::IndexedVersionType::VersionType ver_type; ModPlatform::IndexedVersionType::VersionType ver_type;

View File

@ -18,6 +18,7 @@ struct IndexedVersion {
int fileId; int fileId;
QString version; QString version;
ModPlatform::IndexedVersionType version_type; ModPlatform::IndexedVersionType version_type;
ModPlatform::ModLoaderTypes loaders = {};
QString mcVersion; QString mcVersion;
QString downloadUrl; QString downloadUrl;
}; };

View File

@ -129,7 +129,7 @@ Task::Ptr ModrinthAPI::getModCategories(std::shared_ptr<QByteArray> response)
return netJob; return netJob;
} }
QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByteArray> response) QList<ModPlatform::Category> ModrinthAPI::loadCategories(std::shared_ptr<QByteArray> response, QString projectType)
{ {
QList<ModPlatform::Category> categories; QList<ModPlatform::Category> categories;
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -147,7 +147,7 @@ QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByt
for (auto val : arr) { for (auto val : arr) {
auto cat = Json::requireObject(val); auto cat = Json::requireObject(val);
auto name = Json::requireString(cat, "name"); auto name = Json::requireString(cat, "name");
if (Json::ensureString(cat, "project_type", "") == "mod") if (Json::ensureString(cat, "project_type", "") == projectType)
categories.push_back({ name, name }); categories.push_back({ name, name });
} }
@ -157,4 +157,9 @@ QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByt
qDebug() << doc; qDebug() << doc;
} }
return categories; return categories;
}
QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByteArray> response)
{
return loadCategories(response, "mod");
}; };

View File

@ -31,6 +31,7 @@ class ModrinthAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response); static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
static QList<ModPlatform::Category> loadCategories(std::shared_ptr<QByteArray> response, QString projectType);
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response); static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
public: public:

View File

@ -1,6 +1,7 @@
#include "FlameModel.h" #include "FlameModel.h"
#include <Json.h> #include <Json.h>
#include "Application.h" #include "Application.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
@ -183,34 +184,28 @@ void ListModel::performPaginatedSearch()
return; return;
} }
} }
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network()); ResourceAPI::SortingMethod sort{};
auto searchUrl = QString( sort.index = currentSort + 1;
"https://api.curseforge.com/v1/mods/search?"
"gameId=432&"
"classId=4471&"
"index=%1&"
"pageSize=25&"
"searchFilter=%2&"
"sortField=%3&"
"sortOrder=desc")
.arg(nextSearchOffset)
.arg(currentSearchTerm)
.arg(currentSort + 1);
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response)); auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = FlameAPI::getStaticSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort,
m_filter->loaders, m_filter->versions, "", m_filter->categoryIds });
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed);
} }
void ListModel::searchWithTerm(const QString& term, int sort) void ListModel::searchWithTerm(const QString& term, int sort, std::shared_ptr<ModFilterWidget::Filter> filter, bool filterChanged)
{ {
if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filterChanged) {
return; return;
} }
currentSearchTerm = term; currentSearchTerm = term;
currentSort = sort; currentSort = sort;
m_filter = filter;
if (hasActiveSearchJob()) { if (hasActiveSearchJob()) {
jobPtr->abort(); jobPtr->abort();
searchState = ResetRequested; searchState = ResetRequested;

View File

@ -14,6 +14,7 @@
#include <net/NetJob.h> #include <net/NetJob.h>
#include <functional> #include <functional>
#include "ui/widgets/ModFilterWidget.h"
#include <modplatform/flame/FlamePackIndex.h> #include <modplatform/flame/FlamePackIndex.h>
@ -38,7 +39,7 @@ class ListModel : public QAbstractListModel {
void fetchMore(const QModelIndex& parent) override; void fetchMore(const QModelIndex& parent) override;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, int sort); void searchWithTerm(const QString& term, int sort, std::shared_ptr<ModFilterWidget::Filter> filter, bool filterChanged);
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
@ -65,6 +66,7 @@ class ListModel : public QAbstractListModel {
QString currentSearchTerm; QString currentSearchTerm;
int currentSort = 0; int currentSort = 0;
std::shared_ptr<ModFilterWidget::Filter> m_filter;
int nextSearchOffset = 0; int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
Task::Ptr jobPtr; Task::Ptr jobPtr;

View File

@ -34,10 +34,13 @@
*/ */
#include "FlamePage.h" #include "FlamePage.h"
#include "modplatform/flame/FlamePackIndex.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/ModFilterWidget.h"
#include "ui_FlamePage.h" #include "ui_FlamePage.h"
#include <QKeyEvent> #include <QKeyEvent>
#include <memory>
#include "Application.h" #include "Application.h"
#include "FlameModel.h" #include "FlameModel.h"
@ -88,6 +91,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->setItemDelegate(new ProjectItemDelegate(this));
ui->packDescription->setMetaEntry("FlamePacks"); ui->packDescription->setMetaEntry("FlamePacks");
createFilterWidget();
} }
FlamePage::~FlamePage() FlamePage::~FlamePage()
@ -131,10 +135,35 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch() void FlamePage::triggerSearch()
{ {
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
ui->packView->clearSelection();
ui->packDescription->clear();
ui->versionSelectionBox->clear();
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(),
m_filterWidget->changed());
m_fetch_progress.watch(listModel->activeSearchJob().get()); m_fetch_progress.watch(listModel->activeSearchJob().get());
} }
bool checkMcVersions(std::list<Version> filter, QString value)
{
for (auto mcVersion : filter) {
if (value == mcVersion.toString()) {
return true;
}
}
return filter.empty();
}
bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptr<ModFilterWidget::Filter> filter)
{
if (!filter)
return true;
return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders
(filter->releases.empty() || // releases
std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) &&
checkMcVersions(filter->versions, v.mcVersion)); // mcVersions}
}
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
{ {
ui->versionSelectionBox->clear(); ui->versionSelectionBox->clear();
@ -148,7 +177,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>(); current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>();
if (current.versionsLoaded == false) { if (!current.versionsLoaded || m_filterWidget->changed()) {
qDebug() << "Loading flame modpack versions"; qDebug() << "Loading flame modpack versions";
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
@ -176,6 +205,16 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
qWarning() << "Error while reading flame modpack version: " << e.cause(); qWarning() << "Error while reading flame modpack version: " << e.cause();
} }
auto pred = [this](const Flame::IndexedVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
current.versions.removeIf(pred);
#else
for (auto it = current.versions.begin(); it != current.versions.end();)
if (pred(*it))
it = current.versions.erase(it);
else
++it;
#endif
for (auto version : current.versions) { for (auto version : current.versions) {
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion) auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion)
@ -308,3 +347,25 @@ void FlamePage::setSearchTerm(QString term)
{ {
ui->searchEdit->setText(term); ui->searchEdit->setText(term);
} }
void FlamePage::createFilterWidget()
{
auto widget = ModFilterWidget::create(nullptr, false, this);
m_filterWidget.swap(widget);
auto old = ui->splitter->replaceWidget(0, m_filterWidget.get());
// because we replaced the widget we also need to delete it
if (old) {
delete old;
}
connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); });
connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch);
auto response = std::make_shared<QByteArray>();
m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK);
QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() {
auto categories = FlameAPI::loadModCategories(response);
m_filterWidget->setCategories(categories);
});
m_categoriesTask->start();
}

View File

@ -41,6 +41,7 @@
#include <modplatform/flame/FlamePackIndex.h> #include <modplatform/flame/FlamePackIndex.h>
#include <QTimer> #include <QTimer>
#include "ui/pages/modplatform/ModpackProviderBasePage.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h"
#include "ui/widgets/ModFilterWidget.h"
#include "ui/widgets/ProgressWidget.h" #include "ui/widgets/ProgressWidget.h"
namespace Ui { namespace Ui {
@ -84,6 +85,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage {
void triggerSearch(); void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(int index); void onVersionSelectionChanged(int index);
void createFilterWidget();
private: private:
Ui::FlamePage* ui = nullptr; Ui::FlamePage* ui = nullptr;
@ -97,4 +99,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage {
// Used to do instant searching with a delay to cache quick changes // Used to do instant searching with a delay to cache quick changes
QTimer m_search_timer; QTimer m_search_timer;
unique_qobject_ptr<ModFilterWidget> m_filterWidget;
Task::Ptr m_categoriesTask;
}; };

View File

@ -30,42 +30,59 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="searchEdit"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item> <item>
<widget class="QListView" name="packView"> <widget class="QLineEdit" name="searchEdit">
<property name="horizontalScrollBarPolicy"> <property name="placeholderText">
<enum>Qt::ScrollBarAlwaysOff</enum> <string>Search and filter...</string>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="ProjectDescriptionPage" name="packDescription"> <widget class="QPushButton" name="filterButton">
<property name="openExternalLinks"> <property name="text">
<bool>true</bool> <string>Filter</string>
</property>
<property name="openLinks">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="widget" native="true"/>
<widget class="QListView" name="packView">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
</widget>
<widget class="ProjectDescriptionPage" name="packDescription">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>

View File

@ -215,11 +215,11 @@ unique_qobject_ptr<ModFilterWidget> FlameModPage::createFilterWidget()
void FlameModPage::prepareProviderCategories() void FlameModPage::prepareProviderCategories()
{ {
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto task = FlameAPI::getModCategories(response); m_categoriesTask = FlameAPI::getModCategories(response);
QObject::connect(task.get(), &Task::succeeded, [this, response]() { QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() {
auto categories = FlameAPI::loadModCategories(response); auto categories = FlameAPI::loadModCategories(response);
m_filter_widget->setCategories(categories); m_filter_widget->setCategories(categories);
}); });
task->start(); m_categoriesTask->start();
}; };
} // namespace ResourceDownload } // namespace ResourceDownload

View File

@ -100,6 +100,9 @@ class FlameModPage : public ModPage {
protected: protected:
virtual void prepareProviderCategories() override; virtual void prepareProviderCategories() override;
private:
Task::Ptr m_categoriesTask;
}; };
class FlameResourcePackPage : public ResourcePackResourcePage { class FlameResourcePackPage : public ResourcePackResourcePage {

View File

@ -68,6 +68,40 @@ class VersionBasicModel : public QIdentityProxyModel {
} }
}; };
class AllVersionProxyModel : public QSortFilterProxyModel {
Q_OBJECT
public:
AllVersionProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
int rowCount(const QModelIndex& parent = QModelIndex()) const override { return QSortFilterProxyModel::rowCount(parent) + 1; }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (!index.isValid()) {
return {};
}
if (index.row() == 0) {
if (role == Qt::DisplayRole) {
return tr("All Versions");
}
return {};
}
QModelIndex newIndex = QSortFilterProxyModel::index(index.row() - 1, index.column());
return QSortFilterProxyModel::data(newIndex, role);
}
Qt::ItemFlags flags(const QModelIndex& index) const override
{
if (index.row() == 0) {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
return QSortFilterProxyModel::flags(index);
}
};
ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent)
: QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter())
{ {
@ -76,9 +110,15 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi
m_versions_proxy = new VersionProxyModel(this); m_versions_proxy = new VersionProxyModel(this);
m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release"));
auto proxy = new VersionBasicModel(this); QAbstractProxyModel* proxy = new VersionBasicModel(this);
proxy->setSourceModel(m_versions_proxy); proxy->setSourceModel(m_versions_proxy);
if (!m_instance && !extended) {
auto allVersions = new AllVersionProxyModel(this);
allVersions->setSourceModel(proxy);
proxy = allVersions;
}
if (extended) { if (extended) {
ui->versions->setSourceModel(proxy); ui->versions->setSourceModel(proxy);
ui->versions->setSeparator(", "); ui->versions->setSeparator(", ");
@ -162,18 +202,22 @@ void ModFilterWidget::loadVersionList()
void ModFilterWidget::prepareBasicFilter() void ModFilterWidget::prepareBasicFilter()
{ {
m_filter->hideInstalled = false; if (m_instance) {
m_filter->side = ""; // or "both" m_filter->hideInstalled = false;
auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); m_filter->side = ""; // or "both"
ui->neoForge->setChecked(loaders & ModPlatform::NeoForge); auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value();
ui->forge->setChecked(loaders & ModPlatform::Forge); ui->neoForge->setChecked(loaders & ModPlatform::NeoForge);
ui->fabric->setChecked(loaders & ModPlatform::Fabric); ui->forge->setChecked(loaders & ModPlatform::Forge);
ui->quilt->setChecked(loaders & ModPlatform::Quilt); ui->fabric->setChecked(loaders & ModPlatform::Fabric);
m_filter->loaders = loaders; ui->quilt->setChecked(loaders & ModPlatform::Quilt);
auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->loaders = loaders;
m_filter->versions.emplace_front(def); auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft");
ui->versions->setCheckedItems({ def }); m_filter->versions.emplace_front(def);
ui->version->setCurrentIndex(ui->version->findText(def)); ui->versions->setCheckedItems({ def });
ui->version->setCurrentIndex(ui->version->findText(def));
} else {
ui->hideInstalled->hide();
}
} }
void ModFilterWidget::onShowAllVersionsChanged() void ModFilterWidget::onShowAllVersionsChanged()
@ -249,7 +293,9 @@ void ModFilterWidget::onHideInstalledFilterChanged()
void ModFilterWidget::onVersionFilterTextChanged(const QString& version) void ModFilterWidget::onVersionFilterTextChanged(const QString& version)
{ {
m_filter->versions.clear(); m_filter->versions.clear();
m_filter->versions.emplace_back(version); if (version != tr("All Versions")) {
m_filter->versions.emplace_back(version);
}
m_filter_changed = true; m_filter_changed = true;
emit filterChanged(); emit filterChanged();
} }