feat: add modrinth pack downloading
Things that don't work / work poorly (there's more for sure but those
are the evident ones):
    - Icons are broken in the import dialog
    - No way to search for private packs
    - Icons are not downloaded when downloading a mod
    - No support for multiple download URLs
    - Probably a lot more...
			
			
This commit is contained in:
		@@ -778,6 +778,8 @@ SET(LAUNCHER_SOURCES
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    ui/pages/modplatform/modrinth/ModrinthPage.cpp
 | 
					    ui/pages/modplatform/modrinth/ModrinthPage.cpp
 | 
				
			||||||
    ui/pages/modplatform/modrinth/ModrinthPage.h
 | 
					    ui/pages/modplatform/modrinth/ModrinthPage.h
 | 
				
			||||||
 | 
					    ui/pages/modplatform/modrinth/ModrinthModel.cpp
 | 
				
			||||||
 | 
					    ui/pages/modplatform/modrinth/ModrinthModel.h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ui/pages/modplatform/technic/TechnicModel.cpp
 | 
					    ui/pages/modplatform/technic/TechnicModel.cpp
 | 
				
			||||||
    ui/pages/modplatform/technic/TechnicModel.h
 | 
					    ui/pages/modplatform/technic/TechnicModel.h
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,3 +14,87 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "ModrinthPackManifest.h"
 | 
					#include "ModrinthPackManifest.h"
 | 
				
			||||||
 | 
					#include "Json.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "minecraft/MinecraftInstance.h"
 | 
				
			||||||
 | 
					#include "minecraft/PackProfile.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Modrinth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loadIndexedPack(Modpack& pack, QJsonObject& obj)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    pack.id = Json::ensureString(obj, "project_id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pack.name = Json::ensureString(obj, "title");
 | 
				
			||||||
 | 
					    pack.description = Json::ensureString(obj, "description");
 | 
				
			||||||
 | 
					    pack.authors << Json::ensureString(obj, "author");
 | 
				
			||||||
 | 
					    pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug"));
 | 
				
			||||||
 | 
					    pack.iconUrl = Json::ensureString(obj, "icon_url");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    pack.extra.body = Json::ensureString(obj, "body");
 | 
				
			||||||
 | 
					    pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
 | 
				
			||||||
 | 
					    pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pack.extraInfoLoaded = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    QVector<ModpackVersion> unsortedVersions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto arr = Json::requireArray(doc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto versionIter : arr) {
 | 
				
			||||||
 | 
					        auto obj = Json::requireObject(versionIter);
 | 
				
			||||||
 | 
					        auto file = loadIndexedVersion(obj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!file.id.isEmpty()) // Heuristic to check if the returned value is valid
 | 
				
			||||||
 | 
					            unsortedVersions.append(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
 | 
				
			||||||
 | 
					        // dates are in RFC 3339 format
 | 
				
			||||||
 | 
					        return a.date > b.date;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pack.versions.swap(unsortedVersions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pack.versionsLoaded = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ModpackVersion file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file.name = Json::requireString(obj, "name");
 | 
				
			||||||
 | 
					    file.version = Json::requireString(obj, "version_number");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file.id = Json::requireString(obj, "id");
 | 
				
			||||||
 | 
					    file.project_id = Json::requireString(obj, "project_id");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    file.date = Json::requireString(obj, "date_published");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto files = Json::requireArray(obj, "files");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto file_iter : files) {
 | 
				
			||||||
 | 
					        File indexed_file;
 | 
				
			||||||
 | 
					        auto parent = Json::requireObject(file_iter);
 | 
				
			||||||
 | 
					        if (!Json::ensureBoolean(parent, "primary", false)) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        file.download_url = Json::requireString(parent, "url");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(file.download_url.isEmpty())
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return file;
 | 
				
			||||||
 | 
					}        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace Modrinth
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,18 +15,68 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QMetaType>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <QByteArray>
 | 
					#include <QByteArray>
 | 
				
			||||||
#include <QCryptographicHash>
 | 
					#include <QCryptographicHash>
 | 
				
			||||||
#include <QString>
 | 
					#include <QString>
 | 
				
			||||||
#include <QUrl>
 | 
					#include <QUrl>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MinecraftInstance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Modrinth {
 | 
					namespace Modrinth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct File
 | 
					struct File
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    QString path;
 | 
					    QString path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    QCryptographicHash::Algorithm hashAlgorithm;
 | 
					    QCryptographicHash::Algorithm hashAlgorithm;
 | 
				
			||||||
    QByteArray hash;
 | 
					    QByteArray hash;
 | 
				
			||||||
    // TODO: should this support multiple download URLs, like the JSON does?
 | 
					    // TODO: should this support multiple download URLs, like the JSON does?
 | 
				
			||||||
    QUrl download;
 | 
					    QUrl download;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ModpackExtra {
 | 
				
			||||||
 | 
					    QString body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString sourceUrl;
 | 
				
			||||||
 | 
					    QString wikiUrl;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ModpackVersion {
 | 
				
			||||||
 | 
					    QString name;
 | 
				
			||||||
 | 
					    QString version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString id;
 | 
				
			||||||
 | 
					    QString project_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString download_url;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Modpack {
 | 
				
			||||||
 | 
					    QString id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString name;
 | 
				
			||||||
 | 
					    QString description;
 | 
				
			||||||
 | 
					    QStringList authors;
 | 
				
			||||||
 | 
					    QString iconName;
 | 
				
			||||||
 | 
					    QUrl    iconUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool    versionsLoaded = false;
 | 
				
			||||||
 | 
					    bool    extraInfoLoaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ModpackExtra extra;
 | 
				
			||||||
 | 
					    QVector<ModpackVersion> versions;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loadIndexedPack(Modpack&, QJsonObject&);
 | 
				
			||||||
 | 
					void loadIndexedInfo(Modpack&, QJsonObject&);
 | 
				
			||||||
 | 
					void loadIndexedVersions(Modpack&, QJsonDocument&);
 | 
				
			||||||
 | 
					auto loadIndexedVersion(QJsonObject&) -> ModpackVersion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_METATYPE(Modrinth::Modpack);
 | 
				
			||||||
 | 
					Q_DECLARE_METATYPE(Modrinth::ModpackVersion);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										274
									
								
								launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,274 @@
 | 
				
			|||||||
 | 
					#include "ModrinthModel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "BuildConfig.h"
 | 
				
			||||||
 | 
					#include "Json.h"
 | 
				
			||||||
 | 
					#include "minecraft/MinecraftInstance.h"
 | 
				
			||||||
 | 
					#include "minecraft/PackProfile.h"
 | 
				
			||||||
 | 
					#include "ui/dialogs/ModDownloadDialog.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QMessageBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Modrinth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ModpackListModel::ModpackListModel(ModrinthPage* parent) : QAbstractListModel(parent), m_parent(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto ModpackListModel::debugName() const -> QString
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return m_parent->debugName();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/******** Make data requests ********/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::fetchMore(const QModelIndex& parent)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (parent.isValid())
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    if (nextSearchOffset == 0) {
 | 
				
			||||||
 | 
					        qWarning() << "fetchMore with 0 offset is wrong...";
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    performPaginatedSearch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVariant
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int pos = index.row();
 | 
				
			||||||
 | 
					    if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
 | 
				
			||||||
 | 
					        return QString("INVALID INDEX %1").arg(pos);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Modrinth::Modpack pack = modpacks.at(pos);
 | 
				
			||||||
 | 
					    if (role == Qt::DisplayRole) {
 | 
				
			||||||
 | 
					        return pack.name;
 | 
				
			||||||
 | 
					    } else if (role == 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;
 | 
				
			||||||
 | 
					    } else if (role == Qt::DecorationRole) {
 | 
				
			||||||
 | 
					        // FIXME: help the icons dont have the same size ;-;
 | 
				
			||||||
 | 
					        if (m_logoMap.contains(pack.iconName)) {
 | 
				
			||||||
 | 
					            return (m_logoMap.value(pack.iconName));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
 | 
				
			||||||
 | 
					        ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString());
 | 
				
			||||||
 | 
					        return icon;
 | 
				
			||||||
 | 
					    } else if (role == Qt::UserRole) {
 | 
				
			||||||
 | 
					        QVariant v;
 | 
				
			||||||
 | 
					        v.setValue(pack);
 | 
				
			||||||
 | 
					        return v;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					void ModpackListModel::requestModVersions(ModPlatform::IndexedPack const& current)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() });
 | 
				
			||||||
 | 
					}*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::performPaginatedSearch()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // TODO: Move to standalone API
 | 
				
			||||||
 | 
					    NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network());
 | 
				
			||||||
 | 
					    auto searchAllUrl = QString(
 | 
				
			||||||
 | 
					                            "https://staging-api.modrinth.com/v2/search?"
 | 
				
			||||||
 | 
					                            "query=%1&"
 | 
				
			||||||
 | 
					                            "facets=[[\"project_type:modpack\"]]")
 | 
				
			||||||
 | 
					                            .arg(currentSearchTerm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QObject::connect(netJob, &NetJob::succeeded, this, [this] {
 | 
				
			||||||
 | 
					        QJsonParseError parse_error_all {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all);
 | 
				
			||||||
 | 
					        if (parse_error_all.error != QJsonParseError::NoError) {
 | 
				
			||||||
 | 
					            qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset
 | 
				
			||||||
 | 
					                       << " reason: " << parse_error_all.errorString();
 | 
				
			||||||
 | 
					            qWarning() << m_all_response;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        searchRequestFinished(doc_all);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    jobPtr = netJob;
 | 
				
			||||||
 | 
					    jobPtr->start();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::refresh()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (jobPtr) {
 | 
				
			||||||
 | 
					        jobPtr->abort();
 | 
				
			||||||
 | 
					        searchState = ResetRequested;
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        beginResetModel();
 | 
				
			||||||
 | 
					        modpacks.clear();
 | 
				
			||||||
 | 
					        endResetModel();
 | 
				
			||||||
 | 
					        searchState = None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    nextSearchOffset = 0;
 | 
				
			||||||
 | 
					    performPaginatedSearch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::searchWithTerm(const QString& term, const int sort)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentSearchTerm = term;
 | 
				
			||||||
 | 
					    currentSort = sort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    refresh();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (m_logoMap.contains(logo)) {
 | 
				
			||||||
 | 
					        callback(APPLICATION->metacache()
 | 
				
			||||||
 | 
					                     ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
 | 
				
			||||||
 | 
					                     ->getFullPath());
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        requestLogo(logo, logoUrl);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::requestLogo(QString logo, QString url)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MetaEntryPtr entry =
 | 
				
			||||||
 | 
					        APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
 | 
				
			||||||
 | 
					    auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
 | 
				
			||||||
 | 
					    job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto fullPath = entry->getFullPath();
 | 
				
			||||||
 | 
					    QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
 | 
				
			||||||
 | 
					        job->deleteLater();
 | 
				
			||||||
 | 
					        emit logoLoaded(logo, QIcon(fullPath));
 | 
				
			||||||
 | 
					        if (waitingCallbacks.contains(logo)) {
 | 
				
			||||||
 | 
					            waitingCallbacks.value(logo)(fullPath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QObject::connect(job, &NetJob::failed, this, [this, logo, job] {
 | 
				
			||||||
 | 
					        job->deleteLater();
 | 
				
			||||||
 | 
					        emit logoFailed(logo);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    job->start();
 | 
				
			||||||
 | 
					    m_loadingLogos.append(logo);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/******** Request callbacks ********/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::logoLoaded(QString logo, QIcon out)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    m_loadingLogos.removeAll(logo);
 | 
				
			||||||
 | 
					    m_logoMap.insert(logo, out);
 | 
				
			||||||
 | 
					    for (int i = 0; i < modpacks.size(); i++) {
 | 
				
			||||||
 | 
					        if (modpacks[i].iconName == logo) {
 | 
				
			||||||
 | 
					            emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::logoFailed(QString logo)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    m_failedLogos.append(logo);
 | 
				
			||||||
 | 
					    m_loadingLogos.removeAll(logo);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    jobPtr.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QList<Modrinth::Modpack> newList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto packs_all = doc_all.object().value("hits").toArray();
 | 
				
			||||||
 | 
					    for (auto packRaw : packs_all) {
 | 
				
			||||||
 | 
					        auto packObj = packRaw.toObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Modrinth::Modpack pack;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            Modrinth::loadIndexedPack(pack, packObj);
 | 
				
			||||||
 | 
					            newList.append(pack);
 | 
				
			||||||
 | 
					        } catch (const JSONValidationError& e) {
 | 
				
			||||||
 | 
					            qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (packs_all.size() < 25) {
 | 
				
			||||||
 | 
					        searchState = Finished;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        nextSearchOffset += 25;
 | 
				
			||||||
 | 
					        searchState = CanPossiblyFetchMore;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
 | 
				
			||||||
 | 
					    modpacks.append(newList);
 | 
				
			||||||
 | 
					    endInsertRows();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::searchRequestFailed(QString reason)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (!jobPtr->first()->m_reply) {
 | 
				
			||||||
 | 
					        // Network error
 | 
				
			||||||
 | 
					        QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
 | 
				
			||||||
 | 
					    } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
 | 
				
			||||||
 | 
					        // 409 Gone, notify user to update
 | 
				
			||||||
 | 
					        QMessageBox::critical(nullptr, tr("Error"),
 | 
				
			||||||
 | 
					                              //: %1 refers to the launcher itself
 | 
				
			||||||
 | 
					                              QString("%1 %2")
 | 
				
			||||||
 | 
					                                  .arg(m_parent->displayName())
 | 
				
			||||||
 | 
					                                  .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    jobPtr.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (searchState == ResetRequested) {
 | 
				
			||||||
 | 
					        beginResetModel();
 | 
				
			||||||
 | 
					        modpacks.clear();
 | 
				
			||||||
 | 
					        endResetModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nextSearchOffset = 0;
 | 
				
			||||||
 | 
					        performPaginatedSearch();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        searchState = Finished;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModpackListModel::versionRequestSucceeded(QJsonDocument doc, QString id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    auto& current = m_parent->getCurrent();
 | 
				
			||||||
 | 
					    if (id != current.id) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        // loadIndexedPackVersions(current, arr);
 | 
				
			||||||
 | 
					    } catch (const JSONValidationError& e) {
 | 
				
			||||||
 | 
					        qDebug() << doc;
 | 
				
			||||||
 | 
					        qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // m_parent->updateModVersions();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace Modrinth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/******** Helpers ********/
 | 
				
			||||||
							
								
								
									
										81
									
								
								launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QAbstractListModel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "modplatform/modrinth/ModrinthPackManifest.h"
 | 
				
			||||||
 | 
					#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ModPage;
 | 
				
			||||||
 | 
					class Version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Modrinth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using LogoMap = QMap<QString, QIcon>;
 | 
				
			||||||
 | 
					using LogoCallback = std::function<void (QString)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ModpackListModel : public QAbstractListModel {
 | 
				
			||||||
 | 
					    Q_OBJECT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   public:
 | 
				
			||||||
 | 
					    ModpackListModel(ModrinthPage* parent);
 | 
				
			||||||
 | 
					    ~ModpackListModel() override = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
 | 
				
			||||||
 | 
					    inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
 | 
				
			||||||
 | 
					    inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto debugName() const -> QString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Retrieve information from the model at a given index with the given role */
 | 
				
			||||||
 | 
					    auto data(const QModelIndex& index, int role) const -> QVariant override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Ask the API for more information */
 | 
				
			||||||
 | 
					    void fetchMore(const QModelIndex& parent) override;
 | 
				
			||||||
 | 
					    void refresh();
 | 
				
			||||||
 | 
					    void searchWithTerm(const QString& term, const int sort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   public slots:
 | 
				
			||||||
 | 
					    void searchRequestFinished(QJsonDocument& doc_all);
 | 
				
			||||||
 | 
					    void searchRequestFailed(QString reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void versionRequestSucceeded(QJsonDocument doc, QString addonId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   protected slots:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void logoFailed(QString logo);
 | 
				
			||||||
 | 
					    void logoLoaded(QString logo, QIcon out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void performPaginatedSearch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   protected:
 | 
				
			||||||
 | 
					    void requestLogo(QString file, QString url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline auto getMineVersions() const -> std::list<Version>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   protected:
 | 
				
			||||||
 | 
					    ModrinthPage* m_parent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QList<Modrinth::Modpack> modpacks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LogoMap m_logoMap;
 | 
				
			||||||
 | 
					    QMap<QString, LogoCallback> waitingCallbacks;
 | 
				
			||||||
 | 
					    QStringList m_failedLogos;
 | 
				
			||||||
 | 
					    QStringList m_loadingLogos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QString currentSearchTerm;
 | 
				
			||||||
 | 
					    int currentSort = 0;
 | 
				
			||||||
 | 
					    int nextSearchOffset = 0;
 | 
				
			||||||
 | 
					    enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NetJob::Ptr jobPtr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QByteArray m_all_response;
 | 
				
			||||||
 | 
					    QByteArray m_specific_response;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					}  // namespace ModPlatform
 | 
				
			||||||
@@ -34,14 +34,41 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "ModrinthPage.h"
 | 
					#include "ModrinthPage.h"
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "ui_ModrinthPage.h"
 | 
					#include "ui_ModrinthPage.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <QKeyEvent>
 | 
					#include "ModrinthModel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ModrinthPage::ModrinthPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
 | 
					#include "InstanceImportTask.h"
 | 
				
			||||||
 | 
					#include "Json.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <HoeDown.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QComboBox>
 | 
				
			||||||
 | 
					#include <QKeyEvent>
 | 
				
			||||||
 | 
					#include <QPushButton>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ui->setupUi(this);
 | 
					    ui->setupUi(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch);
 | 
				
			||||||
 | 
					    ui->searchEdit->installEventFilter(this);
 | 
				
			||||||
 | 
					    m_model = new Modrinth::ModpackListModel(this);
 | 
				
			||||||
 | 
					    ui->packView->setModel(m_model);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 | 
				
			||||||
 | 
					    ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Featured"));
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Popularity"));
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Last Updated"));
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Name"));
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Author"));
 | 
				
			||||||
 | 
					    ui->sortByBox->addItem(tr("Sort by Total Downloads"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
 | 
				
			||||||
 | 
					    connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
 | 
				
			||||||
 | 
					    connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ModrinthPage::~ModrinthPage()
 | 
					ModrinthPage::~ModrinthPage()
 | 
				
			||||||
@@ -60,10 +87,10 @@ void ModrinthPage::openedImpl()
 | 
				
			|||||||
    triggerSearch();
 | 
					    triggerSearch();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool ModrinthPage::eventFilter(QObject *watched, QEvent *event)
 | 
					bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
 | 
					    if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
 | 
				
			||||||
        auto *keyEvent = reinterpret_cast<QKeyEvent *>(event);
 | 
					        auto* keyEvent = reinterpret_cast<QKeyEvent*>(event);
 | 
				
			||||||
        if (keyEvent->key() == Qt::Key_Return) {
 | 
					        if (keyEvent->key() == Qt::Key_Return) {
 | 
				
			||||||
            this->triggerSearch();
 | 
					            this->triggerSearch();
 | 
				
			||||||
            keyEvent->accept();
 | 
					            keyEvent->accept();
 | 
				
			||||||
@@ -73,6 +100,176 @@ bool ModrinthPage::eventFilter(QObject *watched, QEvent *event)
 | 
				
			|||||||
    return QObject::eventFilter(watched, event);
 | 
					    return QObject::eventFilter(watched, event);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ModrinthPage::triggerSearch() {
 | 
					void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ui->versionSelectionBox->clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!first.isValid()) {
 | 
				
			||||||
 | 
					        if (isOpened) {
 | 
				
			||||||
 | 
					            dialog->setSuggestedPack();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current = m_model->data(first, Qt::UserRole).value<Modrinth::Modpack>();
 | 
				
			||||||
 | 
					    auto name = current.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!current.extraInfoLoaded) {
 | 
				
			||||||
 | 
					        qDebug() << "Loading modrinth modpack information";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network());
 | 
				
			||||||
 | 
					        auto response = new QByteArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        QString id = current.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        netJob->addNetAction(Net::Download::makeByteArray(QString("https://staging-api.modrinth.com/v2/project/%1").arg(id), response));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
 | 
				
			||||||
 | 
					            if (id != current.id) {
 | 
				
			||||||
 | 
					                return;  // wrong request?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            QJsonParseError parse_error;
 | 
				
			||||||
 | 
					            QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
 | 
				
			||||||
 | 
					            if (parse_error.error != QJsonParseError::NoError) {
 | 
				
			||||||
 | 
					                qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
 | 
				
			||||||
 | 
					                           << " reason: " << parse_error.errorString();
 | 
				
			||||||
 | 
					                qWarning() << *response;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto obj = Json::requireObject(doc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                Modrinth::loadIndexedInfo(current, obj);
 | 
				
			||||||
 | 
					            } catch (const JSONValidationError& e) {
 | 
				
			||||||
 | 
					                qDebug() << *response;
 | 
				
			||||||
 | 
					                qWarning() << "Error while reading modrinth modpack version: " << e.cause();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            updateUI();
 | 
				
			||||||
 | 
					            suggestCurrent();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
 | 
				
			||||||
 | 
					            netJob->deleteLater();
 | 
				
			||||||
 | 
					            delete response;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        netJob->start();
 | 
				
			||||||
 | 
					    } else
 | 
				
			||||||
 | 
					        updateUI();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!current.versionsLoaded) {
 | 
				
			||||||
 | 
					        qDebug() << "Loading modrinth modpack versions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
 | 
				
			||||||
 | 
					        auto response = new QByteArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        QString id = current.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        netJob->addNetAction(
 | 
				
			||||||
 | 
					            Net::Download::makeByteArray(QString("https://staging-api.modrinth.com/v2/project/%1/version").arg(id), response));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
 | 
				
			||||||
 | 
					            if (id != current.id) {
 | 
				
			||||||
 | 
					                return;  // wrong request?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            QJsonParseError parse_error;
 | 
				
			||||||
 | 
					            QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
 | 
				
			||||||
 | 
					            if (parse_error.error != QJsonParseError::NoError) {
 | 
				
			||||||
 | 
					                qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
 | 
				
			||||||
 | 
					                           << " reason: " << parse_error.errorString();
 | 
				
			||||||
 | 
					                qWarning() << *response;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                Modrinth::loadIndexedVersions(current, doc);
 | 
				
			||||||
 | 
					            } catch (const JSONValidationError& e) {
 | 
				
			||||||
 | 
					                qDebug() << *response;
 | 
				
			||||||
 | 
					                qWarning() << "Error while reading modrinth modpack version: " << e.cause();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (auto version : current.versions) {
 | 
				
			||||||
 | 
					                ui->versionSelectionBox->addItem(version.version, QVariant(version.id));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            updateVersionsUI();
 | 
				
			||||||
 | 
					            suggestCurrent();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
 | 
				
			||||||
 | 
					            netJob->deleteLater();
 | 
				
			||||||
 | 
					            delete response;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        netJob->start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        for (auto version : current.versions) {
 | 
				
			||||||
 | 
					            ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        suggestCurrent();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModrinthPage::updateUI()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    QString text = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (current.extra.sourceUrl.isEmpty())
 | 
				
			||||||
 | 
					        text = current.name;
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        text = "<a href=\"" + current.extra.sourceUrl + "\">" + current.name + "</a>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!current.authors.empty()) {
 | 
				
			||||||
 | 
					        // TODO: Implement multiple authors with links
 | 
				
			||||||
 | 
					        text += "<br>" + tr(" by ") + current.authors.at(0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text += "<br>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HoeDown h;
 | 
				
			||||||
 | 
					    text += h.process(current.extra.body.toUtf8());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ui->packDescription->setHtml(text + current.description);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModrinthPage::updateVersionsUI()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // idk
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModrinthPage::suggestCurrent()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (!isOpened) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (selectedVersion.isEmpty()) {
 | 
				
			||||||
 | 
					        dialog->setSuggestedPack();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto& ver : current.versions) {
 | 
				
			||||||
 | 
					        if (ver.id == selectedVersion) {
 | 
				
			||||||
 | 
					            dialog->setSuggestedPack(current.name, new InstanceImportTask(ver.download_url));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModrinthPage::triggerSearch()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ModrinthPage::onVersionSelectionChanged(QString data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (data.isNull() || data.isEmpty()) {
 | 
				
			||||||
 | 
					        selectedVersion = "";
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    selectedVersion = ui->versionSelectionBox->currentData().toString();
 | 
				
			||||||
 | 
					    suggestCurrent();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,48 +39,53 @@
 | 
				
			|||||||
#include "ui/dialogs/NewInstanceDialog.h"
 | 
					#include "ui/dialogs/NewInstanceDialog.h"
 | 
				
			||||||
#include "ui/pages/BasePage.h"
 | 
					#include "ui/pages/BasePage.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "modplatform/modrinth/ModrinthPackManifest.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <QWidget>
 | 
					#include <QWidget>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Ui
 | 
					namespace Ui {
 | 
				
			||||||
{
 | 
					class ModrinthPage;
 | 
				
			||||||
    class ModrinthPage;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ModrinthPage : public QWidget, public BasePage
 | 
					namespace Modrinth {
 | 
				
			||||||
{
 | 
					class ModpackListModel;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ModrinthPage : public QWidget, public BasePage {
 | 
				
			||||||
    Q_OBJECT
 | 
					    Q_OBJECT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					   public:
 | 
				
			||||||
    explicit ModrinthPage(NewInstanceDialog *dialog, QWidget *parent = nullptr);
 | 
					    explicit ModrinthPage(NewInstanceDialog* dialog, QWidget* parent = nullptr);
 | 
				
			||||||
    ~ModrinthPage() override;
 | 
					    ~ModrinthPage() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    QString displayName() const override
 | 
					    QString displayName() const override { return tr("Modrinth"); }
 | 
				
			||||||
    {
 | 
					    QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); }
 | 
				
			||||||
        return tr("Modrinth");
 | 
					    QString id() const override { return "modrinth"; }
 | 
				
			||||||
    }
 | 
					    QString helpPage() const override { return "Modrinth-platform"; }
 | 
				
			||||||
    QIcon icon() const override
 | 
					
 | 
				
			||||||
    {
 | 
					    inline auto debugName() const -> QString { return "Modrinth"; }
 | 
				
			||||||
        return APPLICATION->getThemedIcon("modrinth");
 | 
					    inline auto metaEntryBase() const -> QString { return "ModrinthModpacks"; };
 | 
				
			||||||
    }
 | 
					
 | 
				
			||||||
    QString id() const override
 | 
					    auto getCurrent() -> Modrinth::Modpack& { return current; }
 | 
				
			||||||
    {
 | 
					    void suggestCurrent();
 | 
				
			||||||
        return "modrinth";
 | 
					
 | 
				
			||||||
    }
 | 
					    void updateUI();
 | 
				
			||||||
 | 
					    void updateVersionsUI();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    virtual QString helpPage() const override
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return "Modrinth-platform";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    void retranslate() override;
 | 
					    void retranslate() override;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    void openedImpl() override;
 | 
					    void openedImpl() override;
 | 
				
			||||||
 | 
					    bool eventFilter(QObject* watched, QEvent* event) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool eventFilter(QObject *watched, QEvent *event) override;
 | 
					   private slots:
 | 
				
			||||||
 | 
					    void onSelectionChanged(QModelIndex first, QModelIndex second);
 | 
				
			||||||
private slots:
 | 
					    void onVersionSelectionChanged(QString data);
 | 
				
			||||||
    void triggerSearch();
 | 
					    void triggerSearch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					   private:
 | 
				
			||||||
    Ui::ModrinthPage *ui;
 | 
					    Ui::ModrinthPage* ui;
 | 
				
			||||||
    NewInstanceDialog *dialog;
 | 
					    NewInstanceDialog* dialog;
 | 
				
			||||||
 | 
					    Modrinth::ModpackListModel* m_model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Modrinth::Modpack current;
 | 
				
			||||||
 | 
					    QString selectedVersion;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user