aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui
diff options
context:
space:
mode:
authorSefa Eyeoglu <contact@scrumplex.net>2022-05-17 16:09:22 +0200
committerGitHub <noreply@github.com>2022-05-17 16:09:22 +0200
commitcc13310083fe8d0c3bb423fa3e31db7b1f7b25e1 (patch)
tree19e4613e56c44f0b755a36435f44f3b1d7636058 /launcher/ui
parent1012912272d14d8cc8eb1e009579a997b7a35b7f (diff)
parentddc3b5eb0bbd17edd60d96f5094d65dbdb922765 (diff)
downloadPrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.tar.gz
PrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.tar.bz2
PrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.zip
Merge pull request #565 from Scrumplex/modrinth-packs
Initial Modrinth modpack support
Diffstat (limited to 'launcher/ui')
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp4
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h4
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp2
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp8
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui78
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp82
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h61
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp291
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h123
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp241
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h60
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui112
14 files changed, 1068 insertions, 84 deletions
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index d02ea476..305e85c0 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -13,7 +13,7 @@
#include <QDialogButtonBox>
#include "ui/widgets/PageContainer.h"
-#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthModPage.h"
#include "ModDownloadTask.h"
@@ -98,7 +98,7 @@ void ModDownloadDialog::accept()
QList<BasePage *> ModDownloadDialog::getPages()
{
- modrinthPage = new ModrinthPage(this, m_instance);
+ modrinthPage = new ModrinthModPage(this, m_instance);
flameModPage = new FlameModPage(this, m_instance);
return
{
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index 309d89d0..782dc361 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -16,7 +16,7 @@ class ModDownloadDialog;
class PageContainer;
class QDialogButtonBox;
-class ModrinthPage;
+class ModrinthModPage;
class ModDownloadDialog : public QDialog, public BasePageProvider
{
@@ -50,7 +50,7 @@ private:
QVBoxLayout *m_verticalLayout = nullptr;
- ModrinthPage *modrinthPage = nullptr;
+ ModrinthModPage *modrinthPage = nullptr;
FlameModPage *flameModPage = nullptr;
QHash<QString, ModDownloadTask*> modTask;
BaseInstance *m_instance;
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index b402839c..05ea091d 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -39,6 +39,7 @@
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
@@ -134,6 +135,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
flamePage,
new FtbPage(this),
new LegacyFTB::Page(this),
+ new ModrinthPage(this),
technicPage
};
}
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index 1b53dd40..c7bc13d8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -109,7 +110,11 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
- if(fi.exists() && fi.suffix() == "zip")
+ // mrpack is a modrinth pack
+
+ // Allow non-latin people to use ZIP files!
+ auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
+ if(fi.exists() && (zip || fi.suffix() == "mrpack"))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
@@ -144,6 +149,7 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
+ filter += ";;" + tr("Modrinth pack (*.mrpack)");
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index eb63cbe9..77bc5da5 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -11,28 +11,75 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="1">
- <widget class="QPushButton" name="modpackBtn">
- <property name="text">
- <string>Browse</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
+ <item row="2" column="0">
<widget class="QLineEdit" name="modpackEdit">
<property name="placeholderText">
<string notr="true">http://</string>
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="modpackLabel">
+ <item row="2" column="1">
+ <widget class="QPushButton" name="modpackBtn">
<property name="text">
- <string>Local file or link to a direct download:</string>
+ <string>Browse</string>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" rowspan="2" colspan="2">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>The following file types are implemented (both for local files and URLs):</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>- Curseforge modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>- Modrinth modpacks (ZIP and mrpack)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>- PolyMC / MultiMC exported instances (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>- Technic modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -45,6 +92,13 @@
</property>
</spacer>
</item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="modpackLabel">
+ <property name="text">
+ <string>Local file or link to a direct download:</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
new file mode 100644
index 00000000..1d9f4d60
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ModrinthModModel.h"
+
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+
+namespace Modrinth {
+
+// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
+
+void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ Modrinth::loadIndexedPack(m, obj);
+}
+
+void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+}
+
+auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
new file mode 100644
index 00000000..ae7b0bdd
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ModrinthModPage.h"
+
+namespace Modrinth {
+
+class ListModel : public ModPlatform::ListModel {
+ Q_OBJECT
+
+ public:
+ ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){};
+ ~ListModel() override = default;
+
+ private:
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) 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 Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
new file mode 100644
index 00000000..d3a1f859
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ModrinthModPage.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "ui_ModPage.h"
+
+#include "ModrinthModModel.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+
+ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance)
+ : ModPage(dialog, instance, new ModrinthAPI())
+{
+ listModel = new Modrinth::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ // index is used to set the sorting with the modrinth api
+ ui->sortByBox->addItem(tr("Sort by Relevance"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Follows"));
+ ui->sortByBox->addItem(tr("Sort by Last Updated"));
+ ui->sortByBox->addItem(tr("Sort by Newest"));
+
+ // 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...
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
+ connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
+}
+
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+{
+ auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+
+ auto loaderCompatible = false;
+ for (auto remoteLoader : ver.loaders)
+ {
+ if (loaderStrings.contains(remoteLoader)) {
+ loaderCompatible = true;
+ break;
+ }
+ }
+ return ver.mcVersion.contains(mineVer) && loaderCompatible;
+}
+
+// I don't know why, but doing this on the parent class makes it so that
+// other mod providers start loading before being selected, at least with
+// my Qt, so we need to implement this in every derived class...
+auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
new file mode 100644
index 00000000..b1e72bfe
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "modplatform/ModAPI.h"
+#include "ui/pages/modplatform/ModPage.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+
+class ModrinthModPage : public ModPage {
+ Q_OBJECT
+
+ public:
+ explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance);
+ ~ModrinthModPage() override = default;
+
+ inline auto displayName() const -> QString override { return "Modrinth"; }
+ inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
+ inline auto id() const -> QString override { return "modrinth"; }
+ inline auto helpPage() const -> QString override { return "Mod-platform"; }
+
+ inline auto debugName() const -> QString override { return "Modrinth"; }
+ inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
+
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+
+ auto shouldDisplay() const -> bool override;
+};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index b788860a..7cacf37a 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,30 +14,301 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#include "ModrinthModel.h"
-#include "modplatform/modrinth/ModrinthPackIndex.h"
+#include "BuildConfig.h"
+#include "Json.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+
+#include <QMessageBox>
namespace Modrinth {
-// NOLINTNEXTLINE(modernize-avoid-c-arrays)
-const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
+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) {
+ if (m_logoMap.contains(pack.iconName)) {
+ auto icon = m_logoMap.value(pack.iconName);
+ auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
-void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+ return icon_scaled;
+ }
+ 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::performPaginatedSearch()
+{
+ // TODO: Move to standalone API
+ NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network());
+ auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
+ "/search?"
+ "offset=%1&"
+ "limit=%2&"
+ "query=%3&"
+ "index=%4&"
+ "facets=[[\"project_type:modpack\"]]")
+ .arg(nextSearchOffset)
+ .arg(m_modpacks_per_page)
+ .arg(currentSearchTerm)
+ .arg(currentSort);
+
+ 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()
{
- Modrinth::loadIndexedPack(m, obj);
+ if (jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ } else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ nextSearchOffset = 0;
+ performPaginatedSearch();
}
-void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+static auto sortFromIndex(int index) -> QString
{
- Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+ switch(index){
+ default:
+ case 1:
+ return "relevance";
+ case 2:
+ return "downloads";
+ case 3:
+ return "follows";
+ case 4:
+ return "newest";
+ case 5:
+ return "updated";
+ }
+
+ return {};
}
-auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+void ModpackListModel::searchWithTerm(const QString& term, const int sort)
{
- return obj.object().value("hits").toArray();
+ if(sort > 5 || sort < 0)
+ return;
+
+ auto sort_str = sortFromIndex(sort);
+
+ if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) {
+ return;
+ }
+
+ currentSearchTerm = term;
+ currentSort = sort_str;
+
+ refresh();
+}
+
+void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
+{
+ if (m_logoMap.contains(logo)) {
+ callback(APPLICATION->metacache()
+ ->resolveEntry("ModrinthPacks", 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("ModrinthPacks", 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() < m_modpacks_per_page) {
+ searchState = Finished;
+ } else {
+ nextSearchOffset += m_modpacks_per_page;
+ 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 modpacks."));
+ } 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;
+ }
}
} // namespace Modrinth
+
+/******** Helpers ********/
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 45a6090a..14aa6747 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -1,25 +1,116 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#pragma once
-#include "ModrinthPage.h"
+#include <QAbstractListModel>
+
+#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+
+class ModPage;
+class Version;
namespace Modrinth {
-class ListModel : public ModPlatform::ListModel {
+using LogoMap = QMap<QString, QIcon>;
+using LogoCallback = std::function<void (QString)>;
+
+class ModpackListModel : public QAbstractListModel {
Q_OBJECT
public:
- ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent){};
- ~ListModel() override = default;
-
- private:
- void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
- void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) 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; };
-};
+ 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);
-} // namespace Modrinth
+ 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;
+ QString currentSort;
+ int nextSearchOffset = 0;
+ enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
+
+ NetJob::Ptr jobPtr;
+
+ QByteArray m_all_response;
+ QByteArray m_specific_response;
+
+ int m_modpacks_per_page = 20;
+};
+} // namespace ModPlatform
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 98bde0ae..9bd24b57 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2021-2022 kb1000
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,49 +35,237 @@
*/
#include "ModrinthPage.h"
-#include "modplatform/modrinth/ModrinthAPI.h"
-#include "ui_ModPage.h"
+#include "ui_ModrinthPage.h"
#include "ModrinthModel.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new ModrinthAPI())
+#include "BuildConfig.h"
+#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)
{
- listModel = new Modrinth::ListModel(this);
- ui->packView->setModel(listModel);
+ 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);
- // index is used to set the sorting with the modrinth api
ui->sortByBox->addItem(tr("Sort by Relevance"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
- ui->sortByBox->addItem(tr("Sort by Last Updated"));
ui->sortByBox->addItem(tr("Sort by Newest"));
+ ui->sortByBox->addItem(tr("Sort by Last Updated"));
- // 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...
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);
- connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthPage::onModSelected);
}
-auto ModrinthPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+ModrinthPage::~ModrinthPage()
+{
+ delete ui;
+}
+
+void ModrinthPage::retranslate()
+{
+ ui->retranslateUi(this);
+}
+
+void ModrinthPage::openedImpl()
+{
+ BasePage::openedImpl();
+ triggerSearch();
+}
+
+bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ auto* keyEvent = reinterpret_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ this->triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QObject::eventFilter(watched, event);
+}
+
+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("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, 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("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, 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));
+ }
+
+ 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.projectUrl.isEmpty())
+ text = current.name;
+ else
+ text = "<a href=\"" + current.extra.projectUrl + "\">" + current.name + "</a>";
+
+ // TODO: Implement multiple authors with links
+ text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
+
+ text += "<br>";
+
+ HoeDown h;
+ text += h.process(current.extra.body.toUtf8());
+
+ ui->packDescription->setHtml(text + current.description);
+}
+
+void ModrinthPage::suggestCurrent()
{
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+ if (!isOpened) {
+ return;
+ }
+
+ if (selectedVersion.isEmpty()) {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ for (auto& ver : current.versions) {
+ if (ver.id == selectedVersion) {
+ dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this));
+ auto iconName = current.iconName;
+ m_model->getLogo(iconName, current.iconUrl.toString(),
+ [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); });
- auto loaderCompatible = false;
- for (auto remoteLoader : ver.loaders)
- {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
break;
}
}
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
}
-// I don't know why, but doing this on the parent class makes it so that
-// other mod providers start loading before being selected, at least with
-// my Qt, so we need to implement this in every derived class...
-auto ModrinthPage::shouldDisplay() const -> bool { return true; }
+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();
+}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index e3a0e1f0..db5e1a3d 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2021-2022 kb1000
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,27 +36,56 @@
#pragma once
-#include "modplatform/ModAPI.h"
-#include "ui/pages/modplatform/ModPage.h"
+#include "Application.h"
+#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/pages/BasePage.h"
-#include "modplatform/modrinth/ModrinthAPI.h"
+#include "modplatform/modrinth/ModrinthPackManifest.h"
-class ModrinthPage : public ModPage {
+#include <QWidget>
+
+namespace Ui {
+class ModrinthPage;
+}
+
+namespace Modrinth {
+class ModpackListModel;
+}
+
+class ModrinthPage : public QWidget, public BasePage {
Q_OBJECT
public:
- explicit ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance);
- ~ModrinthPage() override = default;
+ explicit ModrinthPage(NewInstanceDialog* dialog, QWidget* parent = nullptr);
+ ~ModrinthPage() override;
+
+ QString displayName() const override { return tr("Modrinth"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); }
+ QString id() const override { return "modrinth"; }
+ QString helpPage() const override { return "Modrinth-platform"; }
+
+ inline auto debugName() const -> QString { return "Modrinth"; }
+ inline auto metaEntryBase() const -> QString { return "ModrinthModpacks"; };
+
+ auto getCurrent() -> Modrinth::Modpack& { return current; }
+ void suggestCurrent();
+
+ void updateUI();
- inline auto displayName() const -> QString override { return "Modrinth"; }
- inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
- inline auto id() const -> QString override { return "modrinth"; }
- inline auto helpPage() const -> QString override { return "Mod-platform"; }
+ void retranslate() override;
+ void openedImpl() override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
- inline auto debugName() const -> QString override { return "Modrinth"; }
- inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
+ private slots:
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+ void triggerSearch();
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+ private:
+ Ui::ModrinthPage* ui;
+ NewInstanceDialog* dialog;
+ Modrinth::ModpackListModel* m_model;
- auto shouldDisplay() const -> bool override;
+ Modrinth::Modpack current;
+ QString selectedVersion;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
new file mode 100644
index 00000000..4fb59cdf
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ModrinthPage</class>
+ <widget class="QWidget" name="ModrinthPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <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>
+ <property name="uniformItemSizes">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>