Add the option to load decklists from Archidekt, Deckstats, Moxfield, TappedOut in deck editor and lobby (#6030)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 42) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 41) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-13, Intel, 13, Release, 14.3.1) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-14, Apple, 14, Release, 15.4) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-15, Apple, 15, Release, 16.2) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macos-15, Apple, 15, Debug, 16.2) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, 5.15.*, 7) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, 10) (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run

* Add the option to load decklists from Archidekt, Deckstats, Moxfield, TappedOut in deck editor and lobby.

Took 3 hours 34 minutes

Took 9 seconds


Took 12 seconds

* Properly set quantities.

Took 11 minutes

* Warnings.

Took 5 minutes

* Static regexes.

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

* Category loggings and better warnings.

Took 18 minutes


Took 42 seconds

* use loadFromStream_Plain instead of manually adding CardNodes to the DeckList.

Took 30 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
This commit is contained in:
BruebachL 2025-07-15 05:12:25 +02:00 committed by GitHub
parent 83b90d472f
commit e05dad4267
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 444 additions and 3 deletions

View File

@ -16,6 +16,7 @@ set(cockatrice_SOURCES
src/client/network/replay_timeline_widget.cpp
src/client/network/sets_model.cpp
src/client/network/spoiler_background_updater.cpp
src/client/network/parsers/deck_link_to_api_transformer.cpp
src/client/sound_engine.cpp
src/client/tabs/abstract_tab_deck_editor.cpp
src/client/tabs/api/edhrec/tab_edhrec.cpp
@ -165,6 +166,7 @@ set(cockatrice_SOURCES
src/dialogs/dlg_forgot_password_reset.cpp
src/dialogs/dlg_load_deck.cpp
src/dialogs/dlg_load_deck_from_clipboard.cpp
src/dialogs/dlg_load_deck_from_website.cpp
src/dialogs/dlg_load_remote_deck.cpp
src/dialogs/dlg_manage_sets.cpp
src/dialogs/dlg_move_top_cards_until.cpp

View File

@ -19,6 +19,7 @@
#tab_supervisor = true
#dlg_edit_avatar = true
#dlg_load_deck_from_website = true
#dlg_settings = true
#dlg_tip_of_the_day = true
#dlg_update = true

View File

@ -54,6 +54,9 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d
aPrintDeck = new QAction(QString(), this);
connect(aPrintDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actPrintDeck);
aLoadDeckFromWebsite = new QAction(QString(), this);
connect(aLoadDeckFromWebsite, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromWebsite);
aExportDeckDecklist = new QAction(QString(), this);
connect(aExportDeckDecklist, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklist);
@ -97,6 +100,7 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d
addMenu(saveDeckToClipboardMenu);
addSeparator();
addAction(aPrintDeck);
addAction(aLoadDeckFromWebsite);
addMenu(analyzeDeckMenu);
addSeparator();
addAction(deckEditor->filterDockWidget->aClearFilterOne);
@ -166,6 +170,7 @@ void DeckEditorMenu::retranslateUi()
aPrintDeck->setText(tr("&Print deck..."));
aLoadDeckFromWebsite->setText(tr("Load deck from online service..."));
analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)"));

View File

@ -16,8 +16,8 @@ public:
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
*aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo,
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist,
*aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aLoadDeckFromWebsite,
*aExportDeckDecklist, *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu;
void setSaveStatus(bool newStatus);

View File

@ -0,0 +1,61 @@
#include "deck_link_to_api_transformer.h"
#include <QRegularExpression>
namespace DeckLinkToApiTransformer
{
static const QString TAPPEDOUT_BASE = "https://tappedout.net/mtg-decks/";
static const QString TAPPEDOUT_SUFFIX = "/?fmt=txt";
static const QString ARCHIDEKT_BASE = "https://archidekt.com/api/decks/";
static const QString ARCHIDEKT_SUFFIX = "/?format=json";
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
static const QString MOXFIELD_SUFFIX = "/";
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
{
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
QRegularExpressionMatch match;
if ((match = rxTappedOut.match(url)).hasMatch()) {
QString slug = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = TAPPEDOUT_BASE,
.deckID = slug,
.fullUrl = TAPPEDOUT_BASE + slug + TAPPEDOUT_SUFFIX,
.provider = DeckProvider::TappedOut};
return true;
} else if ((match = rxArchidekt.match(url)).hasMatch()) {
QString deckID = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = ARCHIDEKT_BASE,
.deckID = deckID,
.fullUrl = ARCHIDEKT_BASE + deckID + ARCHIDEKT_SUFFIX,
.provider = DeckProvider::Archidekt};
return true;
} else if ((match = rxMoxfield.match(url)).hasMatch()) {
QString deckID = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = MOXFIELD_BASE,
.deckID = deckID,
.fullUrl = MOXFIELD_BASE + deckID + MOXFIELD_SUFFIX,
.provider = DeckProvider::Moxfield};
return true;
} else if ((match = rxDeckstats.match(url)).hasMatch()) {
QString deckPath = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/",
.deckID = deckPath,
.fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX,
.provider = DeckProvider::Deckstats};
return true;
}
return false;
}
} // namespace DeckLinkToApiTransformer

View File

@ -0,0 +1,31 @@
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
#define DECK_LINK_TO_API_TRANSFORMER_H
#include <QString>
enum class DeckProvider
{
TappedOut,
Archidekt,
Moxfield,
Deckstats,
Unknown
};
struct ParsedDeckInfo
{
QString baseUrl;
QString deckID;
QString fullUrl;
DeckProvider provider;
};
namespace DeckLinkToApiTransformer
{
// Returns true if the input URL is recognized and fills outInfo.
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo);
} // namespace DeckLinkToApiTransformer
#endif // DECK_LINK_TO_API_TRANSFORMER_H

View File

@ -0,0 +1,112 @@
#ifndef INTERFACE_JSON_DECK_PARSER_H
#define INTERFACE_JSON_DECK_PARSER_H
#include "../../../deck/deck_loader.h"
#include <QJsonArray>
#include <QJsonObject>
class IJsonDeckParser
{
public:
virtual ~IJsonDeckParser() = default;
virtual DeckLoader *parse(const QJsonObject &obj) = 0;
};
class ArchidektJsonParser : public IJsonDeckParser
{
public:
DeckLoader *parse(const QJsonObject &obj) override
{
DeckLoader *list = new DeckLoader();
QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString();
list->setName(deckName);
list->setComments(deckDescription);
QString outputText;
QTextStream outStream(&outputText);
for (auto entry : obj.value("cards").toArray()) {
auto quantity = entry.toObject().value("quantity").toInt();
auto card = entry.toObject().value("card").toObject();
auto oracleCard = card.value("oracleCard").toObject();
QString cardName = oracleCard.value("name").toString();
QString setName = card.value("edition").toObject().value("editioncode").toString().toUpper();
QString collectorNumber = card.value("collectorNumber").toString();
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
list->loadFromStream_Plain(outStream, false);
list->resolveSetNameAndNumberToProviderID();
return list;
}
};
class MoxfieldJsonParser : public IJsonDeckParser
{
public:
DeckLoader *parse(const QJsonObject &obj) override
{
DeckLoader *list = new DeckLoader();
QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString();
list->setName(deckName);
list->setComments(deckDescription);
QString outputText;
QTextStream outStream(&outputText);
for (auto entry : obj.value("mainboard").toObject()) {
auto quantity = entry.toObject().value("quantity").toInt();
auto card = entry.toObject().value("card").toObject();
QString cardName = card.value("name").toString();
QString setName = card.value("set").toString().toUpper();
QString collectorNumber = card.value("cn").toString();
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
outStream << '\n';
for (auto entry : obj.value("sideboard").toObject()) {
auto quantity = entry.toObject().value("quantity").toInt();
auto card = entry.toObject().value("card").toObject();
QString cardName = card.value("name").toString();
QString setName = card.value("set").toString().toUpper();
QString collectorNumber = card.value("cn").toString();
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
list->loadFromStream_Plain(outStream, false);
list->resolveSetNameAndNumberToProviderID();
QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) {
for (auto it = commandersObj.begin(); it != commandersObj.end(); ++it) {
QJsonObject cardData = it.value().toObject().value("card").toObject();
QString commanderName = cardData.value("name").toString();
QString setName = cardData.value("set").toString().toUpper();
QString collectorNumber = cardData.value("cn").toString();
QString providerId = cardData.value("scryfall_id").toString();
list->setBannerCard(QPair<QString, QString>(commanderName, providerId));
list->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
}
}
return list;
}
};
#endif // INTERFACE_JSON_DECK_PARSER_H

View File

@ -6,6 +6,7 @@
#include "../../deck/deck_stats_interface.h"
#include "../../dialogs/dlg_load_deck.h"
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
#include "../../dialogs/dlg_load_deck_from_website.h"
#include "../../game/cards/card_database_manager.h"
#include "../../game/cards/card_database_model.h"
#include "../../server/pending_command.h"
@ -467,6 +468,28 @@ void AbstractTabDeckEditor::actPrintDeck()
dlg->exec();
}
void AbstractTabDeckEditor::actLoadDeckFromWebsite()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromWebsite dlg(this);
if (!dlg.exec())
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeck());
} else {
setDeck(dlg.getDeck());
setModified(true);
}
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{
// check if deck is not null

View File

@ -102,6 +102,7 @@ protected slots:
void actSaveDeckToClipboardRaw();
void actSaveDeckToClipboardRawNoSetInfo();
void actPrintDeck();
void actLoadDeckFromWebsite();
void actExportDeckDecklist();
void actExportDeckDecklistXyz();
void actAnalyzeDeckDeckstats();

View File

@ -0,0 +1,145 @@
#include "dlg_load_deck_from_website.h"
#include <QApplication>
#include <QClipboard>
#include <QDialogButtonBox>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QNetworkReply>
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
{
nam = new QNetworkAccessManager(this);
layout = new QVBoxLayout(this);
setLayout(layout);
instructionLabel = new QLabel(this);
layout->addWidget(instructionLabel);
urlEdit = new QLineEdit(this);
urlEdit->setText(QApplication::clipboard()->text());
layout->addWidget(urlEdit);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttonBox);
if (testValidUrl()) {
QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection);
hide();
}
retranslateUi();
}
void DlgLoadDeckFromWebsite::retranslateUi()
{
instructionLabel->setText(tr("Paste a link to a decklist site here to import it.\n(Archidekt, Deckstats, Moxfield, "
"and TappedOut are supported.)"));
}
bool DlgLoadDeckFromWebsite::testValidUrl()
{
ParsedDeckInfo info;
return DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info);
}
void DlgLoadDeckFromWebsite::accept()
{
ParsedDeckInfo info;
if (DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info)) {
qCInfo(DlgLoadDeckFromWebsiteLog) << info.baseUrl << info.deckID << info.fullUrl;
auto jsonParser = createParserForProvider(info.provider);
if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) {
qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider";
QMessageBox::warning(this, tr("Load Deck from Website"),
tr("No parser available for this deck provider.\n (Archidekt, Deckstats, Moxfield, "
"and TappedOut are supported.)"));
QDialog::reject();
return;
}
QNetworkRequest request(QUrl(info.fullUrl));
QNetworkReply *reply = nam->get(request);
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qCWarning(DlgLoadDeckFromWebsiteLog) << "Network error:" << reply->errorString();
QMessageBox::warning(this, tr("Load Deck from Website"), tr("Network error: %1").arg(reply->errorString()));
reply->deleteLater();
QDialog::reject();
return;
}
QByteArray responseData = reply->readAll();
reply->deleteLater();
// Special handling for Deckstats and TappedOut .txt
if (info.provider == DeckProvider::Deckstats || info.provider == DeckProvider::TappedOut) {
QString deckText = QString::fromUtf8(responseData);
if (deckText.isEmpty()) {
qCWarning(DlgLoadDeckFromWebsiteLog) << "Response is empty";
QMessageBox::warning(this, tr("Load Deck from Website"), tr("Received empty deck data."));
QDialog::reject();
return;
}
// Parse the plain text deck here
DeckLoader *loader = new DeckLoader();
QTextStream stream(&deckText);
loader->loadFromStream_Plain(stream, false);
loader->resolveSetNameAndNumberToProviderID();
deck = loader;
QDialog::accept();
return;
}
// Normal JSON parsing for other providers
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qCWarning(DlgLoadDeckFromWebsiteLog) << "JSON parse error:" << parseError.errorString();
QMessageBox::warning(this, tr("Load Deck from Website"),
tr("Failed to parse deck data: %1").arg(parseError.errorString()));
QDialog::reject();
return;
}
deck = jsonParser->parse(doc.object());
QDialog::accept();
} else {
qCInfo(DlgLoadDeckFromWebsiteLog) << "URL not recognized";
QMessageBox::warning(this, tr("Load Deck from Website"),
tr("The provided URL is not recognized as a valid deck URL.\n"
"Valid deck URLs look like this:\n\n"
"https://archidekt.com/decks/9999999\n"
"https://deckstats.net/decks/99999/9999999-your-deck-name/en\n"
"https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n"
"https://tappedout.net/mtg-decks/your-deck-name/"));
QDialog::reject();
}
}
QSharedPointer<IJsonDeckParser> DlgLoadDeckFromWebsite::createParserForProvider(DeckProvider provider)
{
switch (provider) {
case DeckProvider::Archidekt:
return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser());
case DeckProvider::Moxfield:
return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser());
default:
return QSharedPointer<IJsonDeckParser>(nullptr);
}
}

View File

@ -0,0 +1,40 @@
#ifndef DLG_LOAD_DECK_FROM_WEBSITE_H
#define DLG_LOAD_DECK_FROM_WEBSITE_H
#include "../client/network/parsers/deck_link_to_api_transformer.h"
#include "../client/network/parsers/interface_json_deck_parser.h"
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QNetworkAccessManager>
#include <QVBoxLayout>
inline Q_LOGGING_CATEGORY(DlgLoadDeckFromWebsiteLog, "dlg_load_deck_from_website");
class DlgLoadDeckFromWebsite : public QDialog
{
Q_OBJECT
public:
explicit DlgLoadDeckFromWebsite(QWidget *parent);
void retranslateUi();
bool testValidUrl();
DeckLoader *deck;
DeckLoader *getDeck()
{
return deck;
}
private:
QNetworkAccessManager *nam;
QVBoxLayout *layout;
QLabel *instructionLabel;
QLineEdit *urlEdit;
public slots:
void accept() override;
QSharedPointer<IJsonDeckParser> createParserForProvider(DeckProvider provider);
};
#endif // DLG_LOAD_DECK_FROM_WEBSITE_H

View File

@ -5,6 +5,7 @@
#include "../../deck/deck_loader.h"
#include "../../dialogs/dlg_load_deck.h"
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
#include "../../dialogs/dlg_load_deck_from_website.h"
#include "../../dialogs/dlg_load_remote_deck.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
@ -54,6 +55,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
loadLocalButton = new QPushButton;
loadRemoteButton = new QPushButton;
loadFromClipboardButton = new QPushButton;
loadFromWebsiteButton = new QPushButton;
unloadDeckButton = new QPushButton;
readyStartButton = new ToggleButton;
forceStartGameButton = new QPushButton;
@ -62,6 +64,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck);
connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck);
connect(loadFromClipboardButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromClipboard);
connect(loadFromWebsiteButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromWebsite);
connect(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart);
connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck);
connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart);
@ -72,6 +75,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
buttonHBox->addWidget(loadLocalButton);
buttonHBox->addWidget(loadRemoteButton);
buttonHBox->addWidget(loadFromClipboardButton);
buttonHBox->addWidget(loadFromWebsiteButton);
buttonHBox->addWidget(unloadDeckButton);
buttonHBox->addWidget(readyStartButton);
buttonHBox->addWidget(sideboardLockButton);
@ -123,6 +127,7 @@ void DeckViewContainer::retranslateUi()
loadLocalButton->setText(tr("Load deck..."));
loadRemoteButton->setText(tr("Load remote deck..."));
loadFromClipboardButton->setText(tr("Load from clipboard..."));
loadFromWebsiteButton->setText(tr("Load from website..."));
unloadDeckButton->setText(tr("Unload deck"));
readyStartButton->setText(tr("Ready to start"));
forceStartGameButton->setText(tr("Force start"));
@ -154,6 +159,7 @@ void DeckViewContainer::switchToDeckSelectView()
setVisibility(loadLocalButton, true);
setVisibility(loadRemoteButton, !parentGame->getIsLocalGame());
setVisibility(loadFromClipboardButton, true);
setVisibility(loadFromWebsiteButton, true);
setVisibility(unloadDeckButton, false);
setVisibility(readyStartButton, false);
setVisibility(sideboardLockButton, false);
@ -179,6 +185,7 @@ void DeckViewContainer::switchToDeckLoadedView()
setVisibility(loadLocalButton, false);
setVisibility(loadRemoteButton, false);
setVisibility(loadFromClipboardButton, false);
setVisibility(loadFromWebsiteButton, false);
setVisibility(unloadDeckButton, true);
setVisibility(readyStartButton, true);
setVisibility(sideboardLockButton, true);
@ -309,6 +316,18 @@ void DeckViewContainer::loadFromClipboard()
loadDeckFromDeckLoader(deck);
}
void DeckViewContainer::loadFromWebsite()
{
auto dlg = DlgLoadDeckFromWebsite(this);
if (!dlg.exec()) {
return;
}
DeckLoader *deck = dlg.getDeck();
loadDeckFromDeckLoader(deck);
}
void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);

View File

@ -44,7 +44,7 @@ class DeckViewContainer : public QWidget
Q_OBJECT
private:
QVBoxLayout *deckViewLayout;
QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton;
QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton, *loadFromWebsiteButton;
QPushButton *unloadDeckButton, *forceStartGameButton;
ToggleButton *readyStartButton, *sideboardLockButton;
DeckView *deckView;
@ -60,6 +60,7 @@ private slots:
void loadLocalDeck();
void loadRemoteDeck();
void loadFromClipboard();
void loadFromWebsite();
void unloadDeck();
void readyStart();
void forceStart();