refactor(RD): clear up sorting methods

This refactors the sorting methods to join every bit of it into a single
list, easing maintanance. It also removes the weird index contraint on
the list of methods by adding an index field to the DS that holds the
method.

Lastly, it puts the available methods on their respective API, so other
resources on the same API can re-use them later on.

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow 2022-12-20 12:15:17 -03:00
parent c8eca4fb85
commit 36571c5e22
No known key found for this signature in database
GPG Key ID: 8D0F221F0A59F469
17 changed files with 93 additions and 61 deletions

View File

@ -54,12 +54,23 @@ class ResourceAPI {
enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) 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.
unsigned int index;
// The real name of the sorting, as used in the respective API specification.
// Used by Modrinth in the API request.
QString name;
// The human-readable name of the sorting, used for display in the UI.
QString readable_name;
};
struct SearchArgs { struct SearchArgs {
ModPlatform::ResourceType type{}; ModPlatform::ResourceType type{};
int offset = 0; int offset = 0;
std::optional<QString> search; std::optional<QString> search;
std::optional<QString> sorting; std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders; std::optional<ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions; std::optional<std::list<Version> > versions;
}; };
@ -95,6 +106,10 @@ class ResourceAPI {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> on_succeed; std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> on_succeed;
}; };
public:
/** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
public slots: public slots:
[[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const
{ {

View File

@ -212,3 +212,18 @@ NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response)
return netJob; return netJob;
} }
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
{ 3, "LastUpdated", QObject::tr("Sort by Last Updated") },
{ 4, "Name", QObject::tr("Sort by Name") },
{ 5, "Author", QObject::tr("Sort by Author") },
{ 6, "TotalDownloads", QObject::tr("Sort by Downloads") },
{ 7, "Category", QObject::tr("Sort by Category") },
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
{
return s_sorts;
}

View File

@ -14,20 +14,9 @@ class FlameAPI : public NetworkResourceAPI {
NetJob::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response); NetJob::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const;
private: [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
static int getSortFieldInt(QString const& sortString)
{
return sortString == "Featured" ? 1
: sortString == "Popularity" ? 2
: sortString == "LastUpdated" ? 3
: sortString == "Name" ? 4
: sortString == "Author" ? 5
: sortString == "TotalDownloads" ? 6
: sortString == "Category" ? 7
: sortString == "GameVersion" ? 8
: 1;
}
private:
static int getClassId(ModPlatform::ResourceType type) static int getClassId(ModPlatform::ResourceType type)
{ {
switch (type) { switch (type) {
@ -62,7 +51,7 @@ class FlameAPI : public NetworkResourceAPI {
if (args.search.has_value()) if (args.search.has_value())
get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); get_arguments.append(QString("searchFilter=%1").arg(args.search.value()));
if (args.sorting.has_value()) if (args.sorting.has_value())
get_arguments.append(QString("sortField=%1").arg(getSortFieldInt(args.sorting.value()))); get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc"); get_arguments.append("sortOrder=desc");
if (args.loaders.has_value()) if (args.loaders.has_value())
get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));

View File

@ -112,3 +112,15 @@ NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response)
return netJob; return netJob;
} }
// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "relevance", QObject::tr("Sort by Relevance") },
{ 2, "downloads", QObject::tr("Sort by Downloads") },
{ 3, "follows", QObject::tr("Sort by Follows") },
{ 4, "newest", QObject::tr("Sort by Last Updated") },
{ 5, "updated", QObject::tr("Sort by Newest") } };
QList<ResourceAPI::SortingMethod> ModrinthAPI::getSortingMethods() const
{
return s_sorts;
}

View File

@ -49,6 +49,8 @@ class ModrinthAPI : public NetworkResourceAPI {
NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
public: public:
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
@ -116,7 +118,7 @@ class ModrinthAPI : public NetworkResourceAPI {
if (args.search.has_value()) if (args.search.has_value())
get_arguments.append(QString("query=%1").arg(args.search.value())); get_arguments.append(QString("query=%1").arg(args.search.value()));
if (args.sorting.has_value()) if (args.sorting.has_value())
get_arguments.append(QString("index=%1").arg(args.sorting.value())); get_arguments.append(QString("index=%1").arg(args.sorting.value().name));
get_arguments.append(QString("facets=%1").arg(createFacets(args))); get_arguments.append(QString("facets=%1").arg(createFacets(args)));
return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&');

View File

@ -20,12 +20,23 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
Q_ASSERT(profile); Q_ASSERT(profile);
Q_ASSERT(m_filter); Q_ASSERT(m_filter);
std::optional<std::list<Version>> versions {}; std::optional<std::list<Version>> versions{};
if (!m_filter->versions.empty()) std::optional<ResourceAPI::SortingMethod> sort{};
versions = m_filter->versions;
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, { // Version filter
getSorts()[currentSort], profile->getModLoaders(), versions }; if (!m_filter->versions.empty())
versions = m_filter->versions;
}
{ // Sorting method
auto sorting_methods = getSortingMethods();
auto method = std::find_if(sorting_methods.begin(), sorting_methods.end(),
[this](auto const& e) { return m_current_sort_index == e.index; });
if (method != sorting_methods.end())
sort = *method;
}
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
} }
ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks()
{ {
@ -44,7 +55,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
Q_ASSERT(profile); Q_ASSERT(profile);
Q_ASSERT(m_filter); Q_ASSERT(m_filter);
std::optional<std::list<Version>> versions {}; std::optional<std::list<Version>> versions{};
if (!m_filter->versions.empty()) if (!m_filter->versions.empty())
versions = m_filter->versions; versions = m_filter->versions;
@ -73,14 +84,14 @@ ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& ent
} }; } };
} }
void ModModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filter_changed)
{ {
if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort && !filter_changed) {
return; return;
} }
setSearchTerm(term); setSearchTerm(term);
currentSort = sort; m_current_sort_index = sort;
refresh(); refresh();
} }

View File

@ -21,7 +21,7 @@ class ModModel : public ResourceModel {
ModModel(const BaseInstance&, ResourceAPI* api); ModModel(const BaseInstance&, ResourceAPI* api);
/* Ask the API for more information */ /* Ask the API for more information */
void searchWithTerm(const QString& term, const int sort, const bool filter_changed); void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
@ -46,11 +46,8 @@ class ModModel : public ResourceModel {
protected: protected:
virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0;
virtual auto getSorts() const -> const char** = 0;
protected: protected:
int currentSort = 0;
std::shared_ptr<ModFilterWidget::Filter> m_filter = nullptr; std::shared_ptr<ModFilterWidget::Filter> m_filter = nullptr;
}; };

View File

@ -100,7 +100,7 @@ void ModPage::triggerSearch()
updateSelectionButton(); updateSelectionButton();
} }
static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed);
m_fetch_progress.watch(&m_model->activeJob()); m_fetch_progress.watch(&m_model->activeJob());
} }

View File

@ -6,7 +6,9 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
class NetJob; class NetJob;
@ -41,6 +43,8 @@ class ResourceModel : public QAbstractListModel {
inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); }
inline Task const& activeJob() { return m_current_job; } inline Task const& activeJob() { return m_current_job; }
[[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); }
public slots: public slots:
void fetchMore(const QModelIndex& parent) override; void fetchMore(const QModelIndex& parent) override;
// NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12
@ -83,6 +87,7 @@ class ResourceModel : public QAbstractListModel {
enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None;
int m_next_search_offset = 0; int m_next_search_offset = 0;
QString m_search_term; QString m_search_term;
unsigned int m_current_sort_index = 0;
std::unique_ptr<ResourceAPI> m_api; std::unique_ptr<ResourceAPI> m_api;

View File

@ -103,6 +103,17 @@ void ResourcePage::setSearchTerm(QString term)
m_ui->searchEdit->setText(term); m_ui->searchEdit->setText(term);
} }
void ResourcePage::addSortings()
{
Q_ASSERT(m_model);
auto sorts = m_model->getSortingMethods();
std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; });
for (auto&& sorting : sorts)
m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index));
}
bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack)
{ {
QVariant v; QVariant v;

View File

@ -52,14 +52,14 @@ class ResourcePage : public QWidget, public BasePage {
[[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack);
[[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack;
[[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; }
[[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; }
protected: protected:
ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); ResourcePage(ResourceDownloadDialog* parent, BaseInstance&);
void addSortings();
public slots: public slots:
virtual void updateUi(); virtual void updateUi();
virtual void updateSelectionButton(); virtual void updateSelectionButton();

View File

@ -7,9 +7,6 @@
namespace ResourceDownload { namespace ResourceDownload {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" };
FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {} FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {}
void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)

View File

@ -21,10 +21,6 @@ class FlameModModel : public ModModel {
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static const char* sorts[6];
inline auto getSorts() const -> const char** override { return sorts; };
}; };
} // namespace ResourceDownload } // namespace ResourceDownload

View File

@ -48,13 +48,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance)
m_model = new FlameModModel(instance); m_model = new FlameModModel(instance);
m_ui->packView->setModel(m_model); m_ui->packView->setModel(m_model);
// index is used to set the sorting with the flame api addSortings();
m_ui->sortByBox->addItem(tr("Sort by Featured"));
m_ui->sortByBox->addItem(tr("Sort by Popularity"));
m_ui->sortByBox->addItem(tr("Sort by Last Updated"));
m_ui->sortByBox->addItem(tr("Sort by Name"));
m_ui->sortByBox->addItem(tr("Sort by Author"));
m_ui->sortByBox->addItem(tr("Sort by Downloads"));
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's contructor... // so it's best not to connect them in the parent's contructor...

View File

@ -23,9 +23,6 @@
namespace ResourceDownload { namespace ResourceDownload {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {} ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {}
void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)

View File

@ -41,10 +41,6 @@ class ModrinthModModel : public ModModel {
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static const char* sorts[5];
inline auto getSorts() const -> const char** override { return sorts; };
}; };
} // namespace ResourceDownload } // namespace ResourceDownload

View File

@ -50,12 +50,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
m_model = new ModrinthModModel(instance); m_model = new ModrinthModModel(instance);
m_ui->packView->setModel(m_model); m_ui->packView->setModel(m_model);
// index is used to set the sorting with the modrinth api addSortings();
m_ui->sortByBox->addItem(tr("Sort by Relevance"));
m_ui->sortByBox->addItem(tr("Sort by Downloads"));
m_ui->sortByBox->addItem(tr("Sort by Follows"));
m_ui->sortByBox->addItem(tr("Sort by Last Updated"));
m_ui->sortByBox->addItem(tr("Sort by Newest"));
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...