From 797681883ba6dc484b13f348c9cc9249a25414a8 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 11 May 2025 23:38:13 +0200 Subject: [PATCH 01/32] CI: Remove Ubuntu 20.04 (EOL May 31st) (#5849) * ubuntu 20.04 eol * Update release_template.md * [skip ci] Delete .ci/Ubuntu20.04 directory --- .ci/Ubuntu20.04/Dockerfile | 26 -------------------------- .ci/release_template.md | 1 - .github/workflows/desktop-build.yml | 5 ----- 3 files changed, 32 deletions(-) delete mode 100644 .ci/Ubuntu20.04/Dockerfile diff --git a/.ci/Ubuntu20.04/Dockerfile b/.ci/Ubuntu20.04/Dockerfile deleted file mode 100644 index 45481d44b..000000000 --- a/.ci/Ubuntu20.04/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:20.04 - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - build-essential \ - ccache \ - clang-format \ - cmake \ - file \ - g++ \ - git \ - liblzma-dev \ - libmariadb-dev-compat \ - libprotobuf-dev \ - libqt5multimedia5-plugins \ - libqt5sql5-mysql \ - libqt5svg5-dev \ - libqt5websockets5-dev \ - protobuf-compiler \ - qt5-default \ - qt5-image-formats-plugins \ - qtmultimedia5-dev \ - qttools5-dev \ - qttools5-dev-tools \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* diff --git a/.ci/release_template.md b/.ci/release_template.md index a7a2d31cb..d2e6716f8 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -20,7 +20,6 @@ Available pre-compiled binaries for installation: LinuxUbuntu 24.04 LTS Noble NumbatUbuntu 22.04 LTS Jammy Jellyfish - • Ubuntu 20.04 LTS Focal FossaDebian 12 BookwormDebian 11 BullseyeFedora 42 diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 7fb403549..587aeffb2 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -110,11 +110,6 @@ jobs: version: 42 package: RPM - - distro: Ubuntu - version: 20.04 - package: DEB - test: skip # Ubuntu 20.04 has a broken Qt for debug builds - - distro: Ubuntu version: 22.04 package: DEB From 3b0c7a3a30852f24d3c4e0b06f5b68061e83aed9 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 11 May 2025 23:39:45 +0200 Subject: [PATCH 02/32] Re-add vcpkg binary caching (#5933) --- .github/workflows/desktop-build.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 587aeffb2..7d7652141 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -382,16 +382,11 @@ jobs: tools: ${{matrix.qt_tools}} modules: ${{matrix.qt_modules}} - # TODO: re-enable when https://github.com/lukka/run-vcpkg/issues/243 is fixed - - if: false - name: Run vcpkg (disabled) - uses: lukka/run-vcpkg@v11 + - name: Setup vcpkg cache + id: vcpkg-cache + uses: TAServers/vcpkg-cache@v3 with: - runVcpkgInstall: true - doNotCache: false - env: - VCPKG_DEFAULT_TRIPLET: 'x64-windows' - VCPKG_DISABLE_METRICS: 1 + token: ${{ secrets.GITHUB_TOKEN }} - name: Build Cockatrice id: build @@ -401,6 +396,8 @@ jobs: CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' CMAKE_GENERATOR_PLATFORM: 'x64' QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}' + VCPKG_DISABLE_METRICS: 1 + VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite' # No need for --parallel flag, MTT is added in the compile script to let cmake/msbuild manage core count, # project and process parallelism: https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/ run: .ci/compile.sh --server --release --test --package From b2749a0c4ef638e074996019c486d54ce576dbe2 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 11 May 2025 23:40:04 +0200 Subject: [PATCH 03/32] Add debug log (#5937) --- .github/ISSUE_TEMPLATE/bug_report.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4744d611a..7d7490eee 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,14 +9,24 @@ assignees: '' --- +sure to post your OS and the file name of the setup binary instead. +--> **System Information:** + +
Debug Log: + + + +
+ _______________________________________________________________________________________ @@ -26,7 +36,7 @@ ________________________________________________________________________________ _______________________________________________________________________________________ - + **Steps to reproduce:** - Do A From 17c767fa425f73e9ef7f77a851a0e94d801a679a Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 12 May 2025 23:36:55 +0200 Subject: [PATCH 04/32] [GDE] Add a group criteria to the deck list model (#5931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a group criteria to the deck list model and a combo box to the deck dock widget to change it. --------- Co-authored-by: Lukas Brübach --- .../deck_editor_deck_dock_widget.cpp | 18 +++++++++++++ .../deck_editor_deck_dock_widget.h | 2 ++ cockatrice/src/deck/deck_list_model.cpp | 26 ++++++++++++++++++- cockatrice/src/deck/deck_list_model.h | 10 +++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 3683666a2..efe1c0bc9 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -104,6 +104,20 @@ void DeckEditorDeckDockWidget::createDeckDock() deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); + activeGroupCriteriaLabel = new QLabel(this); + + activeGroupCriteriaComboBox = new QComboBox(this); + activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE); + activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); + activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); + connect(activeGroupCriteriaComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this]() { + deckModel->setActiveGroupCriteria( + static_cast(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); + deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + deckView->expandAll(); + deckView->expandAll(); + }); + aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement); @@ -144,6 +158,9 @@ void DeckEditorDeckDockWidget::createDeckDock() upperLayout->addWidget(deckTagsDisplayWidget, 3, 1); + upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0); + upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1); + hashLabel1 = new QLabel(); hashLabel1->setObjectName("hashLabel1"); auto *hashSizePolicy = new QSizePolicy(); @@ -578,6 +595,7 @@ void DeckEditorDeckDockWidget::retranslateUi() showBannerCardCheckBox->setText(tr("Show banner card selection menu")); showTagsWidgetCheckBox->setText(tr("Show tags selection menu")); commentsLabel->setText(tr("&Comments:")); + activeGroupCriteriaLabel->setText(tr("Group by:")); hashLabel1->setText(tr("Hash:")); aIncrement->setText(tr("&Increment number")); diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h index c81b4a6fa..27364a662 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -70,6 +70,8 @@ private: DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget; QLabel *hashLabel1; LineEditUnfocusable *hashLabel; + QLabel *activeGroupCriteriaLabel; + QComboBox *activeGroupCriteriaComboBox; QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; diff --git a/cockatrice/src/deck/deck_list_model.cpp b/cockatrice/src/deck/deck_list_model.cpp index 5d8cb60f3..746a1d9e9 100644 --- a/cockatrice/src/deck/deck_list_model.cpp +++ b/cockatrice/src/deck/deck_list_model.cpp @@ -29,6 +29,24 @@ DeckListModel::~DeckListModel() delete root; } +QString DeckListModel::getSortCriteriaForCard(CardInfoPtr info) +{ + if (!info) { + return "unknown"; + } + + switch (activeGroupCriteria) { + case DeckListModelGroupCriteria::MAIN_TYPE: + return info->getMainCardType(); + case DeckListModelGroupCriteria::MANA_COST: + return info->getCmc(); + case DeckListModelGroupCriteria::COLOR: + return info->getColors() == "" ? "Colorless" : info->getColors(); + default: + return "unknown"; + } +} + void DeckListModel::rebuildTree() { beginResetModel(); @@ -49,7 +67,7 @@ void DeckListModel::rebuildTree() } CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); - QString cardType = info ? info->getMainCardType() : "unknown"; + QString cardType = getSortCriteriaForCard(info); auto *cardTypeNode = dynamic_cast(node->findChild(cardType)); @@ -467,6 +485,12 @@ void DeckListModel::sort(int column, Qt::SortOrder order) emit layoutChanged(); } +void DeckListModel::setActiveGroupCriteria(DeckListModelGroupCriteria newCriteria) +{ + activeGroupCriteria = newCriteria; + rebuildTree(); +} + void DeckListModel::cleanList() { setDeckList(new DeckLoader); diff --git a/cockatrice/src/deck/deck_list_model.h b/cockatrice/src/deck/deck_list_model.h index 94b323e18..81ae06508 100644 --- a/cockatrice/src/deck/deck_list_model.h +++ b/cockatrice/src/deck/deck_list_model.h @@ -12,6 +12,13 @@ class CardDatabase; class QPrinter; class QTextCursor; +enum DeckListModelGroupCriteria +{ + MAIN_TYPE, + MANA_COST, + COLOR +}; + class DecklistModelCardNode : public AbstractDecklistCardNode { private: @@ -85,6 +92,7 @@ signals: public: explicit DeckListModel(QObject *parent = nullptr); ~DeckListModel() override; + QString getSortCriteriaForCard(CardInfoPtr info); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -113,10 +121,12 @@ public: QList getCardsAsCardInfoPtrs() const; QList getCardsAsCardInfoPtrsForZone(QString zoneName) const; QList *getZones() const; + void setActiveGroupCriteria(DeckListModelGroupCriteria newCriteria); private: DeckLoader *deckList; InnerDecklistNode *root; + DeckListModelGroupCriteria activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE; int lastKnownColumn; Qt::SortOrder lastKnownOrder; InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); From d51620640b3d6ba671d07ee470ab9083feace38a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 12 May 2025 14:39:58 -0700 Subject: [PATCH 05/32] [VDS] Add setting to show deck filepath in tooltip (#5899) * [VDS] Show deck filepath in tooltip * Make tooltip type into a setting * Fix build failure --- .../deck_preview/deck_preview_widget.cpp | 24 +++++++++++++- .../deck_preview/deck_preview_widget.h | 1 + ...ual_deck_storage_quick_settings_widget.cpp | 31 +++++++++++++++++++ ...isual_deck_storage_quick_settings_widget.h | 15 +++++++++ cockatrice/src/settings/cache_settings.cpp | 7 +++++ cockatrice/src/settings/cache_settings.h | 6 ++++ dbconverter/src/mocks.cpp | 3 ++ tests/carddatabase/mocks.cpp | 3 ++ 8 files changed, 89 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 556968d2b..165160718 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -46,6 +46,8 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, &DeckPreviewWidget::updateTagsVisibility); connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowBannerCardComboBoxChanged, this, &DeckPreviewWidget::updateBannerCardComboBoxVisibility); + connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::deckPreviewTooltipChanged, this, + &DeckPreviewWidget::refreshBannerCardToolTip); layout->addWidget(bannerCardDisplayWidget); } @@ -79,7 +81,6 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardDisplayWidget->setCard(bannerCard); bannerCardDisplayWidget->setFontSize(24); - refreshBannerCardText(); setFilePath(deckLoader->getLastFileName()); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); @@ -104,6 +105,8 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) layout->addWidget(bannerCardLabel); layout->addWidget(bannerCardComboBox); + refreshBannerCardText(); + retranslateUi(); } @@ -184,10 +187,29 @@ void DeckPreviewWidget::setFilePath(const QString &_filePath) filePath = _filePath; } +/** + * Refreshes the banner card text. + * This also calls `refreshBannerCardToolTip`, since those two often need to be updated together. + */ void DeckPreviewWidget::refreshBannerCardText() { bannerCardDisplayWidget->setOverlayText( deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName()); + + refreshBannerCardToolTip(); +} + +void DeckPreviewWidget::refreshBannerCardToolTip() +{ + auto type = visualDeckStorageWidget->settings()->getDeckPreviewTooltip(); + switch (type) { + case VisualDeckStorageQuickSettingsWidget::TooltipType::None: + bannerCardDisplayWidget->setToolTip(""); + break; + case VisualDeckStorageQuickSettingsWidget::TooltipType::Filepath: + bannerCardDisplayWidget->setToolTip(filePath); + break; + } } void DeckPreviewWidget::updateBannerCardComboBox() diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 8b594a9db..7c0ff1ad4 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -49,6 +49,7 @@ signals: public slots: void setFilePath(const QString &filePath); void refreshBannerCardText(); + void refreshBannerCardToolTip(); void updateBannerCardComboBox(); void setBannerCard(int); void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp index 3b9063a9b..cd085a511 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -4,6 +4,7 @@ #include "visual_deck_storage_widget.h" #include +#include #include VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidget *parent) @@ -80,6 +81,26 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel); unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox); + // tooltip selector + auto deckPreviewTooltipWidget = new QWidget(this); + + deckPreviewTooltipLabel = new QLabel(deckPreviewTooltipWidget); + deckPreviewTooltipComboBox = new QComboBox(deckPreviewTooltipWidget); + deckPreviewTooltipComboBox->setFocusPolicy(Qt::StrongFocus); + deckPreviewTooltipComboBox->addItem("", TooltipType::None); + deckPreviewTooltipComboBox->addItem("", TooltipType::Filepath); + + deckPreviewTooltipComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageTooltipType()); + connect(deckPreviewTooltipComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + [this] { emit deckPreviewTooltipChanged(getDeckPreviewTooltip()); }); + connect(deckPreviewTooltipComboBox, QOverload::of(&QComboBox::currentIndexChanged), &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageTooltipType); + + auto deckPreviewTooltipLayout = new QHBoxLayout(deckPreviewTooltipWidget); + deckPreviewTooltipLayout->setContentsMargins(11, 0, 11, 0); + deckPreviewTooltipLayout->addWidget(deckPreviewTooltipLabel); + deckPreviewTooltipLayout->addWidget(deckPreviewTooltipComboBox); + // card size slider cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize()); connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this, @@ -95,6 +116,7 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg this->addSettingsWidget(searchFolderNamesCheckBox); this->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); this->addSettingsWidget(unusedColorIdentityOpacityWidget); + this->addSettingsWidget(deckPreviewTooltipWidget); this->addSettingsWidget(cardSizeWidget); connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, @@ -112,6 +134,10 @@ void VisualDeckStorageQuickSettingsWidget::retranslateUi() drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); + + deckPreviewTooltipLabel->setText(tr("Deck tooltip:")); + deckPreviewTooltipComboBox->setItemText(0, tr("None")); + deckPreviewTooltipComboBox->setItemText(1, tr("Filepath")); } bool VisualDeckStorageQuickSettingsWidget::getShowFolders() const @@ -149,6 +175,11 @@ int VisualDeckStorageQuickSettingsWidget::getUnusedColorIdentitiesOpacity() cons return unusedColorIdentitiesOpacitySpinBox->value(); } +VisualDeckStorageQuickSettingsWidget::TooltipType VisualDeckStorageQuickSettingsWidget::getDeckPreviewTooltip() const +{ + return deckPreviewTooltipComboBox->currentData().value(); +} + int VisualDeckStorageQuickSettingsWidget::getCardSize() const { return cardSizeWidget->getSlider()->value(); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h index da82b177b..d27a9053b 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h @@ -7,6 +7,7 @@ class CardSizeWidget; class QLabel; class QSpinBox; class QCheckBox; +class QComboBox; /** * The VDS's quick settings menu. @@ -25,9 +26,21 @@ class VisualDeckStorageQuickSettingsWidget : public SettingsButtonWidget QCheckBox *searchFolderNamesCheckBox; QLabel *unusedColorIdentitiesOpacityLabel; QSpinBox *unusedColorIdentitiesOpacitySpinBox; + QLabel *deckPreviewTooltipLabel; + QComboBox *deckPreviewTooltipComboBox; CardSizeWidget *cardSizeWidget; public: + /** + * The info to display in the deck preview's banner card tooltip. + */ + enum TooltipType + { + None, + Filepath + }; + Q_ENUM(TooltipType) + explicit VisualDeckStorageQuickSettingsWidget(QWidget *parent = nullptr); void retranslateUi(); @@ -39,6 +52,7 @@ public: bool getShowTagsOnDeckPreviews() const; bool getSearchFolderNames() const; int getUnusedColorIdentitiesOpacity() const; + TooltipType getDeckPreviewTooltip() const; int getCardSize() const; signals: @@ -49,6 +63,7 @@ signals: void showTagsOnDeckPreviewsChanged(bool enabled); void searchFolderNamesChanged(bool enabled); void unusedColorIdentitiesOpacityChanged(int opacity); + void deckPreviewTooltipChanged(TooltipType tooltip); void cardSizeChanged(int scale); }; diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index ef369641b..acd8cd581 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -280,6 +280,7 @@ SettingsCache::SettingsCache() settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool(); visualDeckStorageUnusedColorIdentitiesOpacity = settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt(); + visualDeckStorageTooltipType = settings->value("interface/visualdeckstoragetooltiptype", 0).toInt(); visualDeckStoragePromptForConversion = settings->value("interface/visualdeckstoragepromptforconversion", true).toBool(); visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool(); @@ -774,6 +775,12 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity); } +void SettingsCache::setVisualDeckStorageTooltipType(int value) +{ + visualDeckStorageTooltipType = value; + settings->setValue("interface/visualdeckstoragetooltiptype", visualDeckStorageTooltipType); +} + void SettingsCache::setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion) { visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion; diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 94cb2ed6a..f0861af58 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -150,6 +150,7 @@ private: int visualDeckStorageCardSize; bool visualDeckStorageDrawUnusedColorIdentities; int visualDeckStorageUnusedColorIdentitiesOpacity; + int visualDeckStorageTooltipType; bool visualDeckStoragePromptForConversion; bool visualDeckStorageAlwaysConvert; bool visualDeckStorageInGame; @@ -476,6 +477,10 @@ public: { return visualDeckStorageUnusedColorIdentitiesOpacity; } + int getVisualDeckStorageTooltipType() const + { + return visualDeckStorageTooltipType; + } bool getVisualDeckStoragePromptForConversion() const { return visualDeckStoragePromptForConversion; @@ -852,6 +857,7 @@ public slots: void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities); void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity); + void setVisualDeckStorageTooltipType(int value); void setVisualDeckStoragePromptForConversion(bool _visualDeckStoragePromptForConversion); void setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert); void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 032cdc95e..acd199759 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -247,6 +247,9 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStorageTooltipType(int /* value */) +{ +} void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index db7dd9353..8ac925c0b 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -251,6 +251,9 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStorageTooltipType(int /* value */) +{ +} void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) { } From 5dd027ad639d3a2f160fe3a67bcee85f939b9c2d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 May 2025 11:39:33 -0700 Subject: [PATCH 06/32] [VDS] Deselect tag if clicked while already selected (#5944) --- .../deck_preview/deck_preview_tag_display_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index 4cfd9447d..db466b77a 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -50,10 +50,10 @@ void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event) { switch (event->button()) { case Qt::LeftButton: - setState(TagState::Selected); + setState(state != TagState::Selected ? TagState::Selected : TagState::NotSelected); break; case Qt::RightButton: - setState(TagState::Excluded); + setState(state != TagState::Excluded ? TagState::Excluded : TagState::NotSelected); break; case Qt::MiddleButton: setState(TagState::NotSelected); From 8cc64bf44ea9bb0465645dfcabb1974a78da69bf Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 17 May 2025 19:23:30 -0700 Subject: [PATCH 07/32] Refactor TabGame: inline some QLayout and QWidget class fields (#5942) * inline layouts * inline widgets --- cockatrice/src/client/tabs/tab_game.cpp | 33 +++++++++++++------------ cockatrice/src/client/tabs/tab_game.h | 6 ++--- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 999b5b164..1c8724278 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -1538,7 +1538,7 @@ void TabGame::createPlayAreaWidget(bool bReplay) scene = new GameScene(phasesToolbar, this); gameView = new GameView(scene); - gamePlayAreaVBox = new QVBoxLayout; + auto gamePlayAreaVBox = new QVBoxLayout; gamePlayAreaVBox->setContentsMargins(0, 0, 0, 0); gamePlayAreaVBox->addWidget(gameView); @@ -1594,12 +1594,12 @@ void TabGame::createReplayDock() connect(replayFastForwardButton, &QToolButton::toggled, this, &TabGame::replayFastForwardButtonToggled); // putting everything together - replayControlLayout = new QHBoxLayout; + auto replayControlLayout = new QHBoxLayout; replayControlLayout->addWidget(timelineWidget, 10); replayControlLayout->addWidget(replayPlayButton); replayControlLayout->addWidget(replayFastForwardButton); - replayControlWidget = new QWidget(); + auto replayControlWidget = new QWidget(); replayControlWidget->setObjectName("replayControlWidget"); replayControlWidget->setLayout(replayControlLayout); @@ -1635,13 +1635,13 @@ void TabGame::createCardInfoDock(bool bReplay) Q_UNUSED(bReplay); cardInfoFrameWidget = new CardInfoFrameWidget(); - cardHInfoLayout = new QHBoxLayout; - cardVInfoLayout = new QVBoxLayout; + auto cardHInfoLayout = new QHBoxLayout; + auto cardVInfoLayout = new QVBoxLayout; cardVInfoLayout->setContentsMargins(0, 0, 0, 0); cardVInfoLayout->addWidget(cardInfoFrameWidget); cardVInfoLayout->addLayout(cardHInfoLayout); - cardBoxLayoutWidget = new QWidget; + auto cardBoxLayoutWidget = new QWidget; cardBoxLayoutWidget->setLayout(cardVInfoLayout); cardInfoDock = new QDockWidget(this); @@ -1679,12 +1679,17 @@ void TabGame::createPlayerListDock(bool bReplay) void TabGame::createMessageDock(bool bReplay) { + auto messageLogLayout = new QVBoxLayout; + messageLogLayout->setContentsMargins(0, 0, 0, 0); + messageLog = new MessageLogWidget(tabSupervisor, this); connect(messageLog, &MessageLogWidget::cardNameHovered, cardInfoFrameWidget, qOverload(&CardInfoFrameWidget::setCard)); connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(messageLog, &MessageLogWidget::deleteCardInfoPopup, this, &TabGame::deleteCardInfoPopup); + messageLogLayout->addWidget(messageLog); + if (!bReplay) { connect(messageLog, &MessageLogWidget::openMessageDialog, this, &TabGame::openMessageDialog); connect(messageLog, &MessageLogWidget::addMentionTag, this, &TabGame::addMentionTag); @@ -1698,6 +1703,8 @@ void TabGame::createMessageDock(bool bReplay) connect(gameTimer, &QTimer::timeout, this, &TabGame::incrementGameTime); gameTimer->start(); + messageLogLayout->addWidget(timeElapsedLabel); + sayLabel = new QLabel; sayEdit = new LineEditCompleter; sayEdit->setMaxLength(MAX_TEXT_LENGTH); @@ -1726,20 +1733,14 @@ void TabGame::createMessageDock(bool bReplay) connect(tabSupervisor, &TabSupervisor::adminLockChanged, this, &TabGame::adminLockChanged); connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabGame::actSay); - sayHLayout = new QHBoxLayout; + auto sayHLayout = new QHBoxLayout; sayHLayout->addWidget(sayLabel); sayHLayout->addWidget(sayEdit); + + messageLogLayout->addLayout(sayHLayout); } - messageLogLayout = new QVBoxLayout; - messageLogLayout->setContentsMargins(0, 0, 0, 0); - if (!bReplay) - messageLogLayout->addWidget(timeElapsedLabel); - messageLogLayout->addWidget(messageLog); - if (!bReplay) - messageLogLayout->addLayout(sayHLayout); - - messageLogLayoutWidget = new QWidget; + auto messageLogLayoutWidget = new QWidget; messageLogLayoutWidget->setLayout(messageLogLayout); messageLayoutDock = new QDockWidget(this); diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index 096998728..370634742 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -111,10 +111,8 @@ private: GameScene *scene; GameView *gameView; QMap deckViewContainers; - QVBoxLayout *cardVInfoLayout, *messageLogLayout, *gamePlayAreaVBox, *deckViewContainerLayout; - QHBoxLayout *cardHInfoLayout, *sayHLayout, *mainHLayout, *replayControlLayout; - QWidget *cardBoxLayoutWidget, *messageLogLayoutWidget, *gamePlayAreaWidget, *deckViewContainerWidget, - *replayControlWidget; + QVBoxLayout *deckViewContainerLayout; + QWidget *gamePlayAreaWidget, *deckViewContainerWidget; QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock; QAction *playersSeparator; QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu; From 1eee314d17e05e1a7420925d817b0927489d2ec1 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 17 May 2025 19:23:54 -0700 Subject: [PATCH 08/32] [VDS] Add ability to search by deck contents (#5943) * [VDS] Add ability to search by deck contents * add deck search syntax help * fix build failure --- cockatrice/CMakeLists.txt | 1 + cockatrice/cockatrice.qrc | 1 + cockatrice/resources/config/qtlogging.ini | 2 + cockatrice/resources/help/deck_search.md | 26 +++ .../visual_deck_storage_search_widget.cpp | 29 +-- .../src/game/filters/deck_filter_string.cpp | 167 ++++++++++++++++++ .../src/game/filters/deck_filter_string.h | 51 ++++++ cockatrice/src/game/filters/filter_string.cpp | 4 +- cockatrice/src/game/filters/syntax_help.cpp | 27 ++- cockatrice/src/game/filters/syntax_help.h | 5 + 10 files changed, 297 insertions(+), 16 deletions(-) create mode 100644 cockatrice/resources/help/deck_search.md create mode 100644 cockatrice/src/game/filters/deck_filter_string.cpp create mode 100644 cockatrice/src/game/filters/deck_filter_string.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 50c721a6f..c25b319db 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -190,6 +190,7 @@ set(cockatrice_SOURCES src/game/cards/card_search_model.cpp src/game/deckview/deck_view.cpp src/game/deckview/deck_view_container.cpp + src/game/filters/deck_filter_string.cpp src/game/filters/filter_builder.cpp src/game/filters/filter_card.cpp src/game/filters/filter_string.cpp diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 38c40561f..72ca1d83f 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -379,5 +379,6 @@ resources/tips/tips_of_the_day.xml resources/help/search.md + resources/help/deck_search.md diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index eeae47b7f..2cd3d871a 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -60,4 +60,6 @@ # pixel_map_generator = false +# deck_filter_string = false # filter_string = false +# syntax_help = false diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md new file mode 100644 index 000000000..cb06a40a6 --- /dev/null +++ b/cockatrice/resources/help/deck_search.md @@ -0,0 +1,26 @@ +## Deck Search Syntax Help +----- +The search bar recognizes a set of special commands.
+In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all +searches are case insensitive. +
+
Filename:
+
[red deck wins](#red deck wins) (Any deck filename containing the words red, deck, and wins)
+
["red deck wins"](#%22red deck wins%22) (Any deck filename containing the exact phrase "red deck wins")
+ +
Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
+
[[plains]] (Any deck that contains at least one card with "plains" in its name)
+
[[t:legendary]] (Any deck that contains at least one legendary)
+
[[t:legendary]]>5 (Any card that contains at least 5 legendaries)
+
[[]]:100 (Any deck that contains exactly 100 cards)
+ +
Negate:
+
[soldier -aggro](#soldier -aggro) (Any deck filename that contains "soldier", but not "aggro")
+ +
Branching:
+
[t:aggro OR o:control](#t:aggro OR o:control) (Any deck filename that contains either aggro or control)
+ +
Grouping:
+
red -([[]]:100 or aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
+ +
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp index 9b7f65224..2998845f1 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -1,6 +1,11 @@ #include "visual_deck_storage_search_widget.h" +#include "../../../../game/filters/deck_filter_string.h" +#include "../../../../game/filters/syntax_help.h" #include "../../../../settings/cache_settings.h" +#include "../../pixel_map_generator.h" + +#include /** * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. @@ -17,7 +22,13 @@ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWi setLayout(layout); searchBar = new QLineEdit(this); - searchBar->setPlaceholderText(tr("Search by filename")); + searchBar->setPlaceholderText(tr("Search by filename (or search expression)")); + searchBar->setClearButtonEnabled(true); + searchBar->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); + + auto help = searchBar->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); + connect(help, &QAction::triggered, this, [this] { createDeckSearchSyntaxHelpWindow(searchBar); }); + layout->addWidget(searchBar); // Add a debounce timer for the search bar to limit frequent updates @@ -52,11 +63,11 @@ static QString getFileSearchName(const QString &filePath, bool includeFolderName { QString deckPath = SettingsCache::instance().getDeckPath(); if (includeFolderName && filePath.startsWith(deckPath)) { - return filePath.mid(deckPath.length()).toLower(); + return filePath.mid(deckPath.length()); } QFileInfo fileInfo(filePath); - QString fileName = fileInfo.fileName().toLower(); + QString fileName = fileInfo.fileName(); return fileName; } @@ -64,14 +75,10 @@ void VisualDeckStorageSearchWidget::filterWidgets(QList wid const QString &searchText, bool includeFolderName) { - if (searchText.isEmpty() || searchText.isNull()) { - for (auto widget : widgets) { - widget->filteredBySearch = false; - } - } + auto filterString = DeckFilterString(searchText); - for (auto file : widgets) { - QString fileSearchName = getFileSearchName(file->filePath, includeFolderName); - file->filteredBySearch = !fileSearchName.contains(searchText.toLower()); + for (auto widget : widgets) { + QString fileSearchName = getFileSearchName(widget->filePath, includeFolderName); + widget->filteredBySearch = !filterString.check(widget, {fileSearchName}); } } diff --git a/cockatrice/src/game/filters/deck_filter_string.cpp b/cockatrice/src/game/filters/deck_filter_string.cpp new file mode 100644 index 000000000..52aa15c71 --- /dev/null +++ b/cockatrice/src/game/filters/deck_filter_string.cpp @@ -0,0 +1,167 @@ +#include "deck_filter_string.h" + +#include "../cards/card_database_manager.h" +#include "filter_string.h" +#include "lib/peglib.h" + +static peg::parser search(R"( +Start <- QueryPartList +~ws <- [ ]+ +QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* + +ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart +SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart + +QueryPart <- NotQuery / DeckContentQuery / GenericQuery + +NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart + +DeckContentQuery <- CardSearch NumericExpression? +CardSearch <- '[[' CardFilterString ']]' +CardFilterString <- (!']]'.)* + +GenericQuery <- String + +NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. +NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. +UnescapedStringListPart <- !['":<>=! ]. +SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)* + +String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] ['] + +NumericExpression <- NumericOperator ws? NumericValue +NumericOperator <- [=:] / <[> +NumericValue <- [0-9]+ +)"); + +static std::once_flag init; + +static void setupParserRules() +{ + // plumbing + auto passthru = [](const peg::SemanticValues &sv) -> DeckFilter { + return !sv.empty() ? std::any_cast(sv[0]) : nullptr; + }; + + search["Start"] = passthru; + search["QueryPartList"] = [](const peg::SemanticValues &sv) -> DeckFilter { + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) { + auto matchesFilter = [&deck, &info](const std::any &query) { + return std::any_cast(query)(deck, info); + }; + return std::all_of(sv.begin(), sv.end(), matchesFilter); + }; + }; + search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> DeckFilter { + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) { + auto matchesFilter = [&deck, &info](const std::any &query) { + return std::any_cast(query)(deck, info); + }; + return std::any_of(sv.begin(), sv.end(), matchesFilter); + }; + }; + search["SomewhatComplexQueryPart"] = passthru; + search["QueryPart"] = passthru; + search["NotQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + const auto dependent = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) -> bool { + return !dependent(deck, info); + }; + }; + + search["String"] = [](const peg::SemanticValues &sv) -> QString { + if (sv.choice() == 0) { + return QString::fromStdString(std::string(sv.sv())); + } + + return QString::fromStdString(std::string(sv.token(0))); + }; + + search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher { + const auto arg = std::any_cast(sv[1]); + const auto op = std::any_cast(sv[0]); + + if (op == ">") + return [=](const int s) { return s > arg; }; + if (op == ">=") + return [=](const int s) { return s >= arg; }; + if (op == "<") + return [=](const int s) { return s < arg; }; + if (op == "<=") + return [=](const int s) { return s <= arg; }; + if (op == "=") + return [=](const int s) { return s == arg; }; + if (op == ":") + return [=](const int s) { return s == arg; }; + if (op == "!=") + return [=](const int s) { return s != arg; }; + return [](int) { return false; }; + }; + + search["NumericValue"] = [](const peg::SemanticValues &sv) -> int { + return QString::fromStdString(std::string(sv.sv())).toInt(); + }; + + search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString { + return QString::fromStdString(std::string(sv.sv())); + }; + + // actual functionality + search["DeckContentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto cardFilter = FilterString(std::any_cast(sv[0])); + auto numberMatcher = sv.size() > 1 ? std::any_cast(sv[1]) : [](int count) { return count > 0; }; + + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { + int count = 0; + deck->deckLoader->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) { + auto cardInfoPtr = CardDatabaseManager::getInstance()->getCard(node->getName()); + if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { + count += node->getNumber(); + } + }); + return numberMatcher(count); + }; + }; + + search["CardSearch"] = [](const peg::SemanticValues &sv) -> QString { return std::any_cast(sv[0]); }; + + search["CardFilterString"] = [](const peg::SemanticValues &sv) -> QString { + return QString::fromStdString(std::string(sv.sv())); + }; + + search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto name = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) { + return info.fileSearchName.contains(name, Qt::CaseInsensitive); + }; + }; +} + +DeckFilterString::DeckFilterString() +{ + filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; }; + _error = "Not initialized"; +} + +DeckFilterString::DeckFilterString(const QString &expr) +{ + QByteArray ba = expr.simplified().toUtf8(); + + std::call_once(init, setupParserRules); + + _error = QString(); + + if (ba.isEmpty()) { + filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return true; }; + return; + } + + search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) { + _error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg)); + }); + + if (!search.parse(ba.data(), filter)) { + qCInfo(DeckFilterStringLog).nospace() << "DeckFilterString error for " << expr << "; " << qPrintable(_error); + filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; }; + } +} \ No newline at end of file diff --git a/cockatrice/src/game/filters/deck_filter_string.h b/cockatrice/src/game/filters/deck_filter_string.h new file mode 100644 index 000000000..dfdb1f1a8 --- /dev/null +++ b/cockatrice/src/game/filters/deck_filter_string.h @@ -0,0 +1,51 @@ +#ifndef DECK_FILTER_STRING_H +#define DECK_FILTER_STRING_H + +#include "../../client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h" + +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string"); + +/** + * Extra info relevant to filtering that isn't present in the DeckPreviewWidget + */ +struct ExtraDeckSearchInfo +{ + /** + * The filename used for filtering. Varies based on settings. + */ + QString fileSearchName; +}; + +typedef std::function DeckFilter; + +class DeckFilterString +{ +public: + DeckFilterString(); + explicit DeckFilterString(const QString &expr); + bool check(const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) const + { + return filter(deck, info); + } + + bool valid() const + { + return _error.isEmpty(); + } + + QString error() + { + return _error; + } + +private: + QString _error; + DeckFilter filter; +}; +#endif // DECK_FILTER_STRING_H diff --git a/cockatrice/src/game/filters/filter_string.cpp b/cockatrice/src/game/filters/filter_string.cpp index 01a7b6be9..e99805541 100644 --- a/cockatrice/src/game/filters/filter_string.cpp +++ b/cockatrice/src/game/filters/filter_string.cpp @@ -7,7 +7,7 @@ #include #include -peg::parser search(R"( +static peg::parser search(R"( Start <- QueryPartList ~ws <- [ ]+ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* @@ -63,7 +63,7 @@ NumericOperator <- [=:] / <[> NumericValue <- [0-9]+ )"); -std::once_flag init; +static std::once_flag init; static void setupParserRules() { diff --git a/cockatrice/src/game/filters/syntax_help.cpp b/cockatrice/src/game/filters/syntax_help.cpp index a4eaf9112..9fb918629 100644 --- a/cockatrice/src/game/filters/syntax_help.cpp +++ b/cockatrice/src/game/filters/syntax_help.cpp @@ -9,11 +9,12 @@ * * @return the QTextBrowser */ -static QTextBrowser *createBrowser() +static QTextBrowser *createBrowser(const QString &helpFile) { - QFile file("theme:help/search.md"); + QFile file(helpFile); if (!file.open(QFile::ReadOnly | QFile::Text)) { + qCWarning(SyntaxHelpLog) << "Could not open syntax help file: " << helpFile; return nullptr; } @@ -54,9 +55,29 @@ static QTextBrowser *createBrowser() */ QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit) { - auto browser = createBrowser(); + auto browser = createBrowser("theme:help/search.md"); QObject::connect(browser, &QTextBrowser::anchorClicked, [lineEdit](const QUrl &link) { lineEdit->setText(link.fragment()); }); QObject::connect(lineEdit, &QObject::destroyed, browser, &QTextBrowser::close); return browser; +} + +/** + * Creates the deck search syntax help window and connects its anchorClicked signal to the given QLineEdit. + * The window will automatically close when the QLineEdit is destroyed. + * + * @return the QTextBrowser + */ +QTextBrowser *createDeckSearchSyntaxHelpWindow(QLineEdit *lineEdit) +{ + auto browser = createBrowser("theme:help/deck_search.md"); + QObject::connect(browser, &QTextBrowser::anchorClicked, [lineEdit](const QUrl &link) { + if (link.fragment() == "cardSearchSyntaxHelp") { + createSearchSyntaxHelpWindow(lineEdit); + } else { + lineEdit->setText(link.fragment()); + } + }); + QObject::connect(lineEdit, &QObject::destroyed, browser, &QTextBrowser::close); + return browser; } \ No newline at end of file diff --git a/cockatrice/src/game/filters/syntax_help.h b/cockatrice/src/game/filters/syntax_help.h index 4016e02bb..911c2fdf0 100644 --- a/cockatrice/src/game/filters/syntax_help.h +++ b/cockatrice/src/game/filters/syntax_help.h @@ -2,8 +2,13 @@ #define SEARCH_SYNTAX_HELP_H #include +#include #include +inline Q_LOGGING_CATEGORY(SyntaxHelpLog, "syntax_help"); + QTextBrowser *createSearchSyntaxHelpWindow(QLineEdit *lineEdit); +QTextBrowser *createDeckSearchSyntaxHelpWindow(QLineEdit *lineEdit); + #endif // SEARCH_SYNTAX_HELP_H From 269523a0340765c07288602c3db8ef9c934220b0 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Sat, 17 May 2025 23:24:14 -0300 Subject: [PATCH 09/32] ci: use Ninja on Linux and macOS builds (#5939) * ci: use Ninja on linux and macos * ci: simplify parallelism --- .ci/Arch/Dockerfile | 1 + .ci/Debian11/Dockerfile | 1 + .ci/Debian12/Dockerfile | 5 +++-- .ci/Fedora41/Dockerfile | 1 + .ci/Fedora42/Dockerfile | 1 + .ci/Ubuntu22.04/Dockerfile | 1 + .ci/Ubuntu24.04/Dockerfile | 5 +++-- .ci/compile.sh | 24 ++---------------------- .ci/docker.sh | 1 + .github/workflows/desktop-build.yml | 20 +++++++++----------- 10 files changed, 23 insertions(+), 37 deletions(-) diff --git a/.ci/Arch/Dockerfile b/.ci/Arch/Dockerfile index ed46e08a9..36cf5c4ae 100644 --- a/.ci/Arch/Dockerfile +++ b/.ci/Arch/Dockerfile @@ -7,6 +7,7 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \ git \ gtest \ mariadb-libs \ + ninja \ protobuf \ qt6-base \ qt6-imageformats \ diff --git a/.ci/Debian11/Dockerfile b/.ci/Debian11/Dockerfile index 8c4fbe724..b994863bf 100644 --- a/.ci/Debian11/Dockerfile +++ b/.ci/Debian11/Dockerfile @@ -16,6 +16,7 @@ RUN apt-get update && \ libqt5sql5-mysql \ libqt5svg5-dev \ libqt5websockets5-dev \ + ninja-build \ protobuf-compiler \ qt5-image-formats-plugins \ qtmultimedia5-dev \ diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile index 21c4ffa29..202405b84 100644 --- a/.ci/Debian12/Dockerfile +++ b/.ci/Debian12/Dockerfile @@ -15,13 +15,14 @@ RUN apt-get update && \ libprotobuf-dev \ libqt6multimedia6 \ libqt6sql6-mysql \ - qt6-svg-dev \ - qt6-websockets-dev \ + ninja-build \ protobuf-compiler \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ + qt6-svg-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ + qt6-websockets-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/.ci/Fedora41/Dockerfile b/.ci/Fedora41/Dockerfile index ab23a6052..fc9e86c2e 100644 --- a/.ci/Fedora41/Dockerfile +++ b/.ci/Fedora41/Dockerfile @@ -6,6 +6,7 @@ RUN dnf install -y \ gcc-c++ \ git \ mariadb-devel \ + ninja-build \ protobuf-devel \ qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ qt6-qtimageformats \ diff --git a/.ci/Fedora42/Dockerfile b/.ci/Fedora42/Dockerfile index cf56ff604..ee4a856f2 100644 --- a/.ci/Fedora42/Dockerfile +++ b/.ci/Fedora42/Dockerfile @@ -6,6 +6,7 @@ RUN dnf install -y \ gcc-c++ \ git \ mariadb-devel \ + ninja-build \ protobuf-devel \ qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ qt6-qtimageformats \ diff --git a/.ci/Ubuntu22.04/Dockerfile b/.ci/Ubuntu22.04/Dockerfile index 260f449cb..ff2e6e43b 100644 --- a/.ci/Ubuntu22.04/Dockerfile +++ b/.ci/Ubuntu22.04/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && \ libqt6sql6-mysql \ libqt6svg6-dev \ libqt6websockets6-dev \ + ninja-build \ protobuf-compiler \ qt6-image-formats-plugins \ qt6-l10n-tools \ diff --git a/.ci/Ubuntu24.04/Dockerfile b/.ci/Ubuntu24.04/Dockerfile index d203690e1..809b2e43a 100644 --- a/.ci/Ubuntu24.04/Dockerfile +++ b/.ci/Ubuntu24.04/Dockerfile @@ -15,13 +15,14 @@ RUN apt-get update && \ libprotobuf-dev \ libqt6multimedia6 \ libqt6sql6-mysql \ - qt6-svg-dev \ - qt6-websockets-dev \ + ninja-build \ protobuf-compiler \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ + qt6-svg-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ + qt6-websockets-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/.ci/compile.sh b/.ci/compile.sh index 1588c0d5f..6f51bc476 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -11,9 +11,8 @@ # --debug or --release sets the build type ie CMAKE_BUILD_TYPE # --ccache [] uses ccache and shows stats, optionally provide size # --dir sets the name of the build dir, default is "build" -# --parallel sets how many cores cmake should build with in parallel -# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR PARALLEL_COUNT -# (correspond to args: --debug/--release --install --package --suffix --server --test --ccache --dir --parallel ) +# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR +# (correspond to args: --debug/--release --install --package --suffix --server --test --ccache --dir ) # exitcode: 1 for failure, 3 for invalid arguments # Read arguments @@ -76,15 +75,6 @@ while [[ $# != 0 ]]; do BUILD_DIR="$1" shift ;; - '--parallel') - shift - if [[ $# == 0 ]]; then - echo "::error file=$0::--parallel expects an argument" - exit 3 - fi - PARALLEL_COUNT="$1" - shift - ;; *) echo "::error file=$0::unrecognized option: $1" exit 3 @@ -126,16 +116,6 @@ fi # Add cmake --build flags buildflags=(--config "$BUILDTYPE") -if [[ $PARALLEL_COUNT ]]; then - if [[ $(cmake --build /not_a_dir --parallel 2>&1 | head -1) =~ parallel ]]; then - # workaround for bionic having an old cmake - echo "this version of cmake does not support --parallel, using native build tool -j instead" - buildflags+=(-- -j "$PARALLEL_COUNT") - # note, no normal build flags should be added after this - else - buildflags+=(--parallel "$PARALLEL_COUNT") - fi -fi function ccachestatsverbose() { # note, verbose only works on newer ccache, discard the error diff --git a/.ci/docker.sh b/.ci/docker.sh index c6ec1b774..e6a900cb5 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -147,6 +147,7 @@ function RUN () if [[ $CCACHE_DIR ]]; then args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache") args+=(--env "CCACHE_DIR=/.ccache") + args+=(--env "CMAKE_GENERATOR="Ninja"") fi docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@" return $? diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 7d7652141..59e4d5005 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -129,6 +129,7 @@ jobs: # Cache size over the entire repo is 10Gi: # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CCACHE_SIZE: 500M + CMAKE_GENERATOR: 'Ninja' steps: - name: Checkout @@ -156,9 +157,11 @@ jobs: - name: Build debug and test if: matrix.test != 'skip' shell: bash + env: + CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' run: | source .ci/docker.sh - RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 4 + RUN --server --debug --test --ccache "$CCACHE_SIZE" - name: Build release package id: build @@ -168,10 +171,11 @@ jobs: BUILD_DIR: build SUFFIX: '-${{matrix.distro}}${{matrix.version}}' type: '${{matrix.package}}' + CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' run: | source .ci/docker.sh RUN --server --release --package "$type" --dir "$BUILD_DIR" \ - --ccache "$CCACHE_SIZE" --parallel 4 + --ccache "$CCACHE_SIZE" .ci/name_build.sh - name: Upload artifact @@ -202,7 +206,6 @@ jobs: os: macos-13 xcode: "14.3.1" type: Release - core_count: 4 make_package: 1 - target: 14 @@ -210,7 +213,6 @@ jobs: os: macos-14 xcode: "15.4" type: Release - core_count: 3 make_package: 1 - target: 15 @@ -218,7 +220,6 @@ jobs: os: macos-15 xcode: "16.2" type: Release - core_count: 3 make_package: 1 - target: 15 @@ -226,7 +227,6 @@ jobs: os: macos-15 xcode: "16.2" type: Debug - core_count: 3 name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure @@ -235,6 +235,7 @@ jobs: env: DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer + CMAKE_GENERATOR: 'Ninja' steps: - name: Checkout @@ -262,9 +263,7 @@ jobs: MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} - # macOS runner have 3 cores usually - only the macos-13 image has 4: - # https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories - # https://github.com/actions/runner-images?tab=readme-ov-file#available-images + CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' run: | if [[ -n "$MACOS_CERTIFICATE_NAME" ]] then @@ -276,7 +275,7 @@ jobs: security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain fi - .ci/compile.sh --server --parallel ${{matrix.core_count}} + .ci/compile.sh --server - name: Sign app bundle if: matrix.make_package @@ -394,7 +393,6 @@ jobs: env: PACKAGE_SUFFIX: '-Win${{matrix.target}}' CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' - CMAKE_GENERATOR_PLATFORM: 'x64' QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}' VCPKG_DISABLE_METRICS: 1 VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite' From 207211faccc81096956b8cc4d444b93b0d349c4e Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 18 May 2025 04:25:01 +0200 Subject: [PATCH 10/32] CI: Tweak ccache for Linux builds (#5938) * rework ccache * use branch name --- .github/workflows/desktop-build.yml | 32 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 59e4d5005..f5d3f86eb 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -135,25 +135,20 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Generate cache timestamp - id: cache_timestamp - shell: bash - run: echo "timestamp=$(date -u '+%Y%m%d%H%M%S')" >>"$GITHUB_OUTPUT" - - - name: Restore cache - uses: actions/cache@v4 - env: - timestamp: ${{steps.cache_timestamp.outputs.timestamp}} - with: - path: ${{env.CACHE}} - key: docker-${{matrix.distro}}${{matrix.version}}-cache-${{env.timestamp}} - restore-keys: | - docker-${{matrix.distro}}${{matrix.version}}-cache- - - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image shell: bash run: source .ci/docker.sh --build + - name: Restore compiler cache (ccache) + id: ccache_restore + uses: actions/cache/restore@v4 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + with: + path: ${{env.CACHE}} + key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}} + restore-keys: ccache-${{matrix.distro}}${{matrix.version}}- + - name: Build debug and test if: matrix.test != 'skip' shell: bash @@ -178,6 +173,13 @@ jobs: --ccache "$CCACHE_SIZE" .ci/name_build.sh + - name: Save compiler cache (ccache) + if: github.ref == 'refs/heads/master' + uses: actions/cache/save@v4 + with: + path: ${{env.CACHE}} + key: ${{ steps.ccache_restore.outputs.cache-primary-key }} + - name: Upload artifact if: matrix.package != 'skip' uses: actions/upload-artifact@v4 From cfbe59868bd0f12a59d9c1f4eb386d71597b84ba Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 18 May 2025 04:25:13 +0200 Subject: [PATCH 11/32] [VDS] Add prompt before overwriting existing files when converting to .cod (#5926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add prompt before overwriting existing files. * Lint. --------- Co-authored-by: Lukas Brübach --- .../deck_preview_deck_tags_display_widget.cpp | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index 8c4723c37..d446589d0 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) : QWidget(_parent), deckList(nullptr) @@ -77,6 +78,21 @@ static QStringList getAllFiles(const QString &filePath) return allFiles; } +bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) +{ + QFileInfo fileInfo(filePath); + QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); + + if (QFile::exists(newFileName)) { + QMessageBox::StandardButton reply = + QMessageBox::question(parent, QObject::tr("Overwrite Existing File?"), + QObject::tr("A .cod version of this deck already exists. Overwrite it?"), + QMessageBox::Yes | QMessageBox::No); + return reply == QMessageBox::Yes; + } + return true; // Safe to proceed +} + void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() { if (qobject_cast(parentWidget())) { @@ -91,6 +107,10 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() // Retrieve saved preference if the prompt is disabled if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) + return; + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); deckPreviewWidget->refreshBannerCardText(); @@ -100,6 +120,10 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() // Show the dialog to the user DialogConvertDeckToCodFormat conversionDialog(parentWidget()); if (conversionDialog.exec() == QDialog::Accepted) { + + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) + return; + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); deckPreviewWidget->refreshBannerCardText(); From b270562a44e81951704214e2c947e3aed3fac84e Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 18 May 2025 19:05:27 -0700 Subject: [PATCH 12/32] Fix wrong Message Dock widget order from #5942 (#5946) * fix * refactor --- cockatrice/src/client/tabs/tab_game.cpp | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 1c8724278..40c25067c 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -1682,20 +1682,8 @@ void TabGame::createMessageDock(bool bReplay) auto messageLogLayout = new QVBoxLayout; messageLogLayout->setContentsMargins(0, 0, 0, 0); - messageLog = new MessageLogWidget(tabSupervisor, this); - connect(messageLog, &MessageLogWidget::cardNameHovered, cardInfoFrameWidget, - qOverload(&CardInfoFrameWidget::setCard)); - connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup); - connect(messageLog, &MessageLogWidget::deleteCardInfoPopup, this, &TabGame::deleteCardInfoPopup); - - messageLogLayout->addWidget(messageLog); - + // clock if (!bReplay) { - connect(messageLog, &MessageLogWidget::openMessageDialog, this, &TabGame::openMessageDialog); - connect(messageLog, &MessageLogWidget::addMentionTag, this, &TabGame::addMentionTag); - connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this, - &TabGame::actCompleterChanged); - timeElapsedLabel = new QLabel; timeElapsedLabel->setAlignment(Qt::AlignCenter); gameTimer = new QTimer(this); @@ -1704,7 +1692,26 @@ void TabGame::createMessageDock(bool bReplay) gameTimer->start(); messageLogLayout->addWidget(timeElapsedLabel); + } + // message log + messageLog = new MessageLogWidget(tabSupervisor, this); + connect(messageLog, &MessageLogWidget::cardNameHovered, cardInfoFrameWidget, + qOverload(&CardInfoFrameWidget::setCard)); + connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup); + connect(messageLog, &MessageLogWidget::deleteCardInfoPopup, this, &TabGame::deleteCardInfoPopup); + + if (!bReplay) { + connect(messageLog, &MessageLogWidget::openMessageDialog, this, &TabGame::openMessageDialog); + connect(messageLog, &MessageLogWidget::addMentionTag, this, &TabGame::addMentionTag); + connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this, + &TabGame::actCompleterChanged); + } + + messageLogLayout->addWidget(messageLog); + + // chat entry + if (!bReplay) { sayLabel = new QLabel; sayEdit = new LineEditCompleter; sayEdit->setMaxLength(MAX_TEXT_LENGTH); @@ -1740,6 +1747,7 @@ void TabGame::createMessageDock(bool bReplay) messageLogLayout->addLayout(sayHLayout); } + // dock auto messageLogLayoutWidget = new QWidget; messageLogLayoutWidget->setLayout(messageLogLayout); From 46643065eff1d3ce20995fef76cb485511c7369c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 18 May 2025 19:06:18 -0700 Subject: [PATCH 13/32] Refactor banner card ComboBox (#5947) --- .../deck_editor/deck_editor_deck_dock_widget.cpp | 13 ++++--------- .../deck_preview/deck_preview_widget.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index efe1c0bc9..0cbeb2b50 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -303,11 +303,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() }); for (const auto &pair : pairList) { - QVariantMap dataMap; - dataMap["name"] = pair.first; - dataMap["uuid"] = pair.second; - - bannerCardComboBox->addItem(pair.first, dataMap); + bannerCardComboBox->addItem(pair.first, QVariant::fromValue(pair)); } // Try to restore the previous selection by finding the currentText @@ -315,7 +311,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() if (restoredIndex != -1) { bannerCardComboBox->setCurrentIndex(restoredIndex); if (deckModel->getDeckList()->getBannerCard().second != - bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap()["uuid"].toString()) { + bannerCardComboBox->currentData().value>().second) { setBannerCard(restoredIndex); } } else { @@ -335,9 +331,8 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) { - QVariantMap itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap(); - deckModel->getDeckList()->setBannerCard( - QPair(itemData["name"].toString(), itemData["uuid"].toString())); + auto cardAndId = bannerCardComboBox->currentData().value>(); + deckModel->getDeckList()->setBannerCard(cardAndId); emit deckModified(); } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 165160718..7fc5c729f 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -282,11 +282,11 @@ void DeckPreviewWidget::updateBannerCardComboBox() void DeckPreviewWidget::setBannerCard(int /* changedIndex */) { - QVariant itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()); - deckLoader->setBannerCard(QPair(bannerCardComboBox->currentText(), itemData.toString())); + auto nameAndId = bannerCardComboBox->currentData().value>(); + deckLoader->setBannerCard(nameAndId); deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); - bannerCardDisplayWidget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( - bannerCardComboBox->currentText(), itemData.toString())); + bannerCardDisplayWidget->setCard( + CardDatabaseManager::getInstance()->getCardByNameAndProviderId(nameAndId.first, nameAndId.second)); } void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) From a5638ccc3b3acb49ffc8f3e9aa10c69a8888d0a4 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 18 May 2025 19:06:32 -0700 Subject: [PATCH 14/32] Parent the "new sets found" dialog (#5948) --- cockatrice/src/client/ui/window_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index 7dabc96c6..721881c89 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -1099,7 +1099,7 @@ void MainWindow::cardDatabaseLoadingFailed() void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames) { - QMessageBox msgBox; + QMessageBox msgBox(this); msgBox.setWindowTitle(tr("New sets found")); msgBox.setIcon(QMessageBox::Question); msgBox.setText(tr("%n new set(s) found in the card database\n" From d729df5cbadd21ecc74592545a924201d317ca9c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 19 May 2025 05:09:03 -0700 Subject: [PATCH 15/32] Add missing shortcuts for Game Lobby buttons (#5951) --- cockatrice/src/game/deckview/deck_view_container.cpp | 2 ++ cockatrice/src/settings/shortcuts_settings.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 81ad8d322..64574ed87 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -198,8 +198,10 @@ void DeckViewContainer::refreshShortcuts() ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); + unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton")); readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); + forceStartGameButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/forceStartGameButton")); } /** diff --git a/cockatrice/src/settings/shortcuts_settings.h b/cockatrice/src/settings/shortcuts_settings.h index 595932623..1407f5923 100644 --- a/cockatrice/src/settings/shortcuts_settings.h +++ b/cockatrice/src/settings/shortcuts_settings.h @@ -267,6 +267,9 @@ private: {"DeckViewContainer/loadRemoteButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Remote Deck..."), parseSequenceString("Ctrl+Alt+O"), ShortcutGroup::Game_Lobby)}, + {"DeckViewContainer/unloadDeckButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unload Deck"), + parseSequenceString("Ctrl+Alt+U"), + ShortcutGroup::Game_Lobby)}, {"DeckViewContainer/readyStartButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Ready to Start"), parseSequenceString("Ctrl+Shift+S"), ShortcutGroup::Game_Lobby)}, @@ -274,6 +277,9 @@ private: ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Sideboard Lock"), parseSequenceString("Ctrl+Shift+B"), ShortcutGroup::Game_Lobby)}, + {"DeckViewContainer/forceStartGameButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Force Start"), + parseSequenceString(""), + ShortcutGroup::Game_Lobby)}, {"Player/aCCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Green Counter"), parseSequenceString("Ctrl+>"), ShortcutGroup::Card_Counters)}, From 50d3dfb98be3733734e1be9f00970b1f881f58f5 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 20 May 2025 07:36:17 -0700 Subject: [PATCH 16/32] Load deck from clipboard in Game Lobby (#5950) --- .../src/game/deckview/deck_view_container.cpp | 42 +++++++++++++++---- .../src/game/deckview/deck_view_container.h | 5 ++- cockatrice/src/settings/shortcuts_settings.h | 4 ++ common/decklist.cpp | 4 +- common/decklist.h | 4 +- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 64574ed87..4afd07943 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -4,6 +4,7 @@ #include "../../client/ui/picture_loader/picture_loader.h" #include "../../deck/deck_loader.h" #include "../../dialogs/dlg_load_deck.h" +#include "../../dialogs/dlg_load_deck_from_clipboard.h" #include "../../dialogs/dlg_load_remote_deck.h" #include "../../server/pending_command.h" #include "../../settings/cache_settings.h" @@ -52,6 +53,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) { loadLocalButton = new QPushButton; loadRemoteButton = new QPushButton; + loadFromClipboardButton = new QPushButton; unloadDeckButton = new QPushButton; readyStartButton = new ToggleButton; forceStartGameButton = new QPushButton; @@ -59,6 +61,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(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart); connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck); connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); @@ -68,6 +71,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) auto *buttonHBox = new QHBoxLayout; buttonHBox->addWidget(loadLocalButton); buttonHBox->addWidget(loadRemoteButton); + buttonHBox->addWidget(loadFromClipboardButton); buttonHBox->addWidget(unloadDeckButton); buttonHBox->addWidget(readyStartButton); buttonHBox->addWidget(sideboardLockButton); @@ -118,6 +122,7 @@ void DeckViewContainer::retranslateUi() { loadLocalButton->setText(tr("Load deck...")); loadRemoteButton->setText(tr("Load remote deck...")); + loadFromClipboardButton->setText(tr("Load from clipboard...")); unloadDeckButton->setText(tr("Unload deck")); readyStartButton->setText(tr("Ready to start")); forceStartGameButton->setText(tr("Force start")); @@ -148,6 +153,7 @@ void DeckViewContainer::switchToDeckSelectView() setVisibility(loadLocalButton, true); setVisibility(loadRemoteButton, !parentGame->getIsLocalGame()); + setVisibility(loadFromClipboardButton, true); setVisibility(unloadDeckButton, false); setVisibility(readyStartButton, false); setVisibility(sideboardLockButton, false); @@ -172,6 +178,7 @@ void DeckViewContainer::switchToDeckLoadedView() setVisibility(loadLocalButton, false); setVisibility(loadRemoteButton, false); + setVisibility(loadFromClipboardButton, false); setVisibility(unloadDeckButton, true); setVisibility(readyStartButton, true); setVisibility(sideboardLockButton, true); @@ -198,6 +205,7 @@ void DeckViewContainer::refreshShortcuts() ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); + loadFromClipboardButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadFromClipboardButton")); unloadDeckButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/unloadDeckButton")); readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); @@ -249,19 +257,27 @@ void DeckViewContainer::loadLocalDeck() void DeckViewContainer::loadDeckFromFile(const QString &filePath) { DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(filePath); - QString deckString; DeckLoader deck; - bool error = !deck.loadFromFile(filePath, fmt, true); - if (!error) { - deckString = deck.writeToString_Native(); - error = deckString.length() > MAX_FILE_LENGTH; - } - if (error) { + bool success = deck.loadFromFile(filePath, fmt, true); + + if (!success) { QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); return; } + loadDeckFromDeckLoader(&deck); +} + +void DeckViewContainer::loadDeckFromDeckLoader(const DeckLoader *deck) +{ + QString deckString = deck->writeToString_Native(); + + if (deckString.length() > MAX_FILE_LENGTH) { + QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size.")); + return; + } + Command_DeckSelect cmd; cmd.set_deck(deckString.toStdString()); PendingCommand *pend = parentGame->prepareGameCommand(cmd); @@ -281,6 +297,18 @@ void DeckViewContainer::loadRemoteDeck() } } +void DeckViewContainer::loadFromClipboard() +{ + auto dlg = DlgLoadDeckFromClipboard(this); + + if (!dlg.exec()) { + return; + } + + DeckLoader *deck = dlg.getDeckList(); + loadDeckFromDeckLoader(deck); +} + void DeckViewContainer::deckSelectFinished(const Response &r) { const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game/deckview/deck_view_container.h index c841c5934..d25a43d0d 100644 --- a/cockatrice/src/game/deckview/deck_view_container.h +++ b/cockatrice/src/game/deckview/deck_view_container.h @@ -44,7 +44,8 @@ class DeckViewContainer : public QWidget Q_OBJECT private: QVBoxLayout *deckViewLayout; - QPushButton *loadLocalButton, *loadRemoteButton, *unloadDeckButton, *forceStartGameButton; + QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton; + QPushButton *unloadDeckButton, *forceStartGameButton; ToggleButton *readyStartButton, *sideboardLockButton; DeckView *deckView; VisualDeckStorageWidget *visualDeckStorageWidget; @@ -58,6 +59,7 @@ private slots: void switchToDeckLoadedView(); void loadLocalDeck(); void loadRemoteDeck(); + void loadFromClipboard(); void unloadDeck(); void readyStart(); void forceStart(); @@ -81,6 +83,7 @@ public: public slots: void loadDeckFromFile(const QString &filePath); + void loadDeckFromDeckLoader(const DeckLoader *deck); }; #endif // DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/settings/shortcuts_settings.h b/cockatrice/src/settings/shortcuts_settings.h index 1407f5923..596781cff 100644 --- a/cockatrice/src/settings/shortcuts_settings.h +++ b/cockatrice/src/settings/shortcuts_settings.h @@ -267,6 +267,10 @@ private: {"DeckViewContainer/loadRemoteButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Remote Deck..."), parseSequenceString("Ctrl+Alt+O"), ShortcutGroup::Game_Lobby)}, + {"DeckViewContainer/loadFromClipboardButton", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."), + parseSequenceString("Ctrl+Shift+V"), + ShortcutGroup::Game_Lobby)}, {"DeckViewContainer/unloadDeckButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unload Deck"), parseSequenceString("Ctrl+Alt+U"), ShortcutGroup::Game_Lobby)}, diff --git a/common/decklist.cpp b/common/decklist.cpp index 6f27d2895..a7da934f4 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -441,7 +441,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } -void DeckList::write(QXmlStreamWriter *xml) +void DeckList::write(QXmlStreamWriter *xml) const { xml->writeStartElement("cockatrice_deck"); xml->writeAttribute("version", "1"); @@ -508,7 +508,7 @@ bool DeckList::loadFromString_Native(const QString &nativeString) return loadFromXml(&xml); } -QString DeckList::writeToString_Native() +QString DeckList::writeToString_Native() const { QString result; QXmlStreamWriter xml(&result); diff --git a/common/decklist.h b/common/decklist.h index 989c3e7fe..a2d58de47 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -345,10 +345,10 @@ public: } bool readElement(QXmlStreamReader *xml); - void write(QXmlStreamWriter *xml); + void write(QXmlStreamWriter *xml) const; bool loadFromXml(QXmlStreamReader *xml); bool loadFromString_Native(const QString &nativeString); - QString writeToString_Native(); + QString writeToString_Native() const; bool loadFromFile_Native(QIODevice *device); bool saveToFile_Native(QIODevice *device); bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata); From 452bf61ef91c5d66d37e3c99d4bde82d375d9b09 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Fri, 23 May 2025 01:01:53 +0200 Subject: [PATCH 17/32] FIX: re-add CMAKE_GENERATOR_PLATFORM: 'x64' (#5954) --- .github/workflows/desktop-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index f5d3f86eb..70acd3900 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -395,6 +395,7 @@ jobs: env: PACKAGE_SUFFIX: '-Win${{matrix.target}}' CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' + CMAKE_GENERATOR_PLATFORM: 'x64' QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}' VCPKG_DISABLE_METRICS: 1 VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite' From cee67f430108baecadfab691fd65cf1a2392dd66 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Sat, 24 May 2025 15:24:15 +0200 Subject: [PATCH 18/32] ci: use ccache on macos (#5952) --- .github/workflows/desktop-build.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 70acd3900..1a9f18661 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -235,6 +235,7 @@ jobs: runs-on: ${{matrix.os}} continue-on-error: ${{matrix.allow-failure == 'yes'}} env: + CCACHE_DIR: /tmp/${{matrix.os}}-${{matrix.type}}-cache DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer CMAKE_GENERATOR: 'Ninja' @@ -251,7 +252,17 @@ jobs: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 run: | brew update - brew install protobuf qt --force-bottle + brew install ccache protobuf qt --force-bottle + + - name: Restore compiler cache (ccache) + id: ccache_restore + uses: actions/cache/restore@v4 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + with: + path: ${{env.CCACHE_DIR}} + key: ccache-${{matrix.os}}-${{matrix.type}}-${{env.BRANCH_NAME}} + restore-keys: ccache-${{matrix.os}}-${{matrix.type}}- - name: Build & Sign on Xcode ${{matrix.xcode}} shell: bash @@ -277,7 +288,14 @@ jobs: security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain fi - .ci/compile.sh --server + .ci/compile.sh --server --ccache "$CCACHE_SIZE" + + - name: Save compiler cache (ccache) + if: github.ref == 'refs/heads/master' + uses: actions/cache/save@v4 + with: + path: ${{env.CCACHE_DIR}} + key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Sign app bundle if: matrix.make_package From 2cc75658415618b4209740b767cd6a21cf6dcd36 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 7 Jun 2025 00:41:31 +0200 Subject: [PATCH 19/32] Only sign & notarize on master or tags (#5958) --- .github/workflows/desktop-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 1a9f18661..0d7ef4bf3 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -298,7 +298,7 @@ jobs: key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Sign app bundle - if: matrix.make_package + if: matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) env: MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} @@ -310,7 +310,7 @@ jobs: fi - name: Notarize app bundle - if: matrix.make_package + if: matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) env: MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} From 1b40c9e692b9904c53a571002dbea8e888147d3e Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 7 Jun 2025 00:42:55 +0200 Subject: [PATCH 20/32] CI: Some cleanup (#5959) * Update desktop-build.yml * Update desktop-build.yml * use CMAKE_GENERATOR in docker.sh * revert to mtime --- .ci/docker.sh | 4 +++- .github/workflows/desktop-build.yml | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.ci/docker.sh b/.ci/docker.sh index e6a900cb5..a9fcfcc5b 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -147,7 +147,9 @@ function RUN () if [[ $CCACHE_DIR ]]; then args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache") args+=(--env "CCACHE_DIR=/.ccache") - args+=(--env "CMAKE_GENERATOR="Ninja"") + fi + if [[ -n "$CMAKE_GENERATOR" ]]; then + args+=(--env "CMAKE_GENERATOR=$CMAKE_GENERATOR") fi docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@" return $? diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 0d7ef4bf3..54a380683 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -125,7 +125,7 @@ jobs: continue-on-error: ${{matrix.allow-failure == 'yes'}} env: NAME: ${{matrix.distro}}${{matrix.version}} - CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work? + CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache # Cache size over the entire repo is 10Gi: # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CCACHE_SIZE: 500M @@ -135,10 +135,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image - shell: bash - run: source .ci/docker.sh --build - - name: Restore compiler cache (ccache) id: ccache_restore uses: actions/cache/restore@v4 @@ -149,6 +145,10 @@ jobs: key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}} restore-keys: ccache-${{matrix.distro}}${{matrix.version}}- + - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image + shell: bash + run: source .ci/docker.sh --build + - name: Build debug and test if: matrix.test != 'skip' shell: bash @@ -165,11 +165,11 @@ jobs: env: BUILD_DIR: build SUFFIX: '-${{matrix.distro}}${{matrix.version}}' - type: '${{matrix.package}}' + package: '${{matrix.package}}' CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}' run: | source .ci/docker.sh - RUN --server --release --package "$type" --dir "$BUILD_DIR" \ + RUN --server --release --package "$package" --dir "$BUILD_DIR" \ --ccache "$CCACHE_SIZE" .ci/name_build.sh @@ -235,7 +235,8 @@ jobs: runs-on: ${{matrix.os}} continue-on-error: ${{matrix.allow-failure == 'yes'}} env: - CCACHE_DIR: /tmp/${{matrix.os}}-${{matrix.type}}-cache + CCACHE_DIR: ${{github.workspace}}/.ccache/${{matrix.os}}-${{matrix.type}} + CCACHE_SIZE: 500M DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer CMAKE_GENERATOR: 'Ninja' @@ -269,7 +270,6 @@ jobs: id: build env: BUILDTYPE: '${{matrix.type}}' - MAKE_TEST: 1 MAKE_PACKAGE: '${{matrix.make_package}}' PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}' MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} @@ -288,7 +288,7 @@ jobs: security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain fi - .ci/compile.sh --server --ccache "$CCACHE_SIZE" + .ci/compile.sh --server --test --ccache "$CCACHE_SIZE" - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' @@ -350,7 +350,7 @@ jobs: if-no-files-found: error - name: Upload to release - if: matrix.package != 'skip' && needs.configure.outputs.tag != null + if: matrix.make_package && needs.configure.outputs.tag != null shell: bash env: GH_TOKEN: ${{github.token}} @@ -438,7 +438,7 @@ jobs: if-no-files-found: error - name: Upload to release - if: matrix.package != 'skip' && needs.configure.outputs.tag != null + if: needs.configure.outputs.tag != null shell: bash env: GH_TOKEN: ${{github.token}} From 1e0a356cd27e7cdaadd96c6ca2a30a34a2ae70cf Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 9 Jun 2025 02:05:24 +0200 Subject: [PATCH 21/32] fix transifex link (#5969) --- .ci/update_translation_source_strings_template.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/translations-pull.yml | 2 +- README.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/update_translation_source_strings_template.md b/.ci/update_translation_source_strings_template.md index 8b89948c6..3598b3efd 100644 --- a/.ci/update_translation_source_strings_template.md +++ b/.ci/update_translation_source_strings_template.md @@ -10,5 +10,5 @@ Last changes are based on commit {{ .commit }}. *This PR is automatically generated and updated by the workflow at `.github/workflows/translations-push.yml`. Review [action runs][2].*
*After merging, all changes to the source language are available for translation at [Transifex][1] shortly.* -[1]: https://app.transifex.com/cockatrice/cockatrice/ +[1]: https://explore.transifex.com/cockatrice/cockatrice/ [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-push.yml?query=branch%3Amaster diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fd9691673..c5354332b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,6 +4,6 @@ contact_links: url: https://discord.gg/3Z9yzmA about: Need help with using the client? Want to find some games? Try the Discord server! - name: 🌐 Translations (Help improve the localization of the app) - url: https://www.transifex.com/cockatrice/cockatrice/ + url: https://explore.transifex.com/cockatrice/cockatrice/ # it is not possible to add a link to the wiki to this description about: For more information and guidance check our Translation FAQ on our wiki! diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 3aad12998..f06637b47 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -51,7 +51,7 @@ jobs: *This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*
*After merging, all new languages and translations are available in the next build.* - [1]: https://app.transifex.com/cockatrice/cockatrice/ + [1]: https://explore.transifex.com/cockatrice/cockatrice/ [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster labels: | CI diff --git a/README.md b/README.md index acc2e4110..3e415fa23 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,9 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/ Made with contrib.rocks -### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://transifex.com/cockatrice/cockatrice/) +### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) -Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://transifex.com/cockatrice/cockatrice/).
+Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!
From 87767be4a66c8cfd68a6f6b7b19ac9335047f59f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:28:59 -0700 Subject: [PATCH 22/32] Put mutex around returning cards on concede (#5964) --- common/server_player.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/server_player.cpp b/common/server_player.cpp index 01f6947fa..13a5405a6 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -920,6 +920,8 @@ Server_Player::cmdConcede(const Command_Concede & /*cmd*/, ResponseContainer & / game->removeArrowsRelatedToPlayer(ges, this); game->unattachCards(ges, this); + playerMutex.lock(); + // Return cards to their rightful owners before conceding the game static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; for (const auto &card : zones.value("table")->getCards()) { @@ -959,6 +961,8 @@ Server_Player::cmdConcede(const Command_Concede & /*cmd*/, ResponseContainer & / } } + playerMutex.unlock(); + // All borrowed cards have been returned, can now continue cleanup process clearZones(); From aa41eb5da4a7cb62f891297bbaae1490f0794709 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 9 Jun 2025 04:29:49 +0200 Subject: [PATCH 23/32] Add build attestations for releases (#5967) --- .github/workflows/desktop-build.yml | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 54a380683..cec2fa44e 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -181,6 +181,7 @@ jobs: key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Upload artifact + id: upload_artifact if: matrix.package != 'skip' uses: actions/upload-artifact@v4 with: @@ -189,6 +190,7 @@ jobs: if-no-files-found: error - name: Upload to release + id: upload_release if: matrix.package != 'skip' && needs.configure.outputs.tag != null shell: bash env: @@ -198,6 +200,20 @@ jobs: asset_name: ${{steps.build.outputs.name}} run: gh release upload "$tag_name" "$asset_path#$asset_name" + - name: Attest binary provenance + id: attestation + if: steps.upload_release.outcome == 'success' + uses: actions/attest-build-provenance@v2 + with: + subject-path: ${{steps.build.outputs.path}} + subject-name: ${{steps.build.outputs.name}} + subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }} + + - name: Verify binary attestation + if: steps.attestation.outcome == 'success' + shell: bash + run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice + build-macos: strategy: fail-fast: false @@ -342,6 +358,7 @@ jobs: fi - name: Upload artifact + id: upload_artifact if: matrix.make_package uses: actions/upload-artifact@v4 with: @@ -350,6 +367,7 @@ jobs: if-no-files-found: error - name: Upload to release + id: upload_release if: matrix.make_package && needs.configure.outputs.tag != null shell: bash env: @@ -359,6 +377,20 @@ jobs: asset_name: ${{steps.build.outputs.name}} run: gh release upload "$tag_name" "$asset_path#$asset_name" + - name: Attest binary provenance + id: attestation + if: steps.upload_release.outcome == 'success' + uses: actions/attest-build-provenance@v2 + with: + subject-path: ${{steps.build.outputs.path}} + subject-name: ${{steps.build.outputs.name}} + subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }} + + - name: Verify binary attestation + if: steps.attestation.outcome == 'success' + shell: bash + run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice + build-windows: strategy: fail-fast: false @@ -422,6 +454,7 @@ jobs: run: .ci/compile.sh --server --release --test --package - name: Upload artifact + id: upload_artifact uses: actions/upload-artifact@v4 with: name: Windows${{matrix.target}}-installer @@ -438,6 +471,7 @@ jobs: if-no-files-found: error - name: Upload to release + id: upload_release if: needs.configure.outputs.tag != null shell: bash env: @@ -446,3 +480,17 @@ jobs: asset_path: ${{steps.build.outputs.path}} asset_name: ${{steps.build.outputs.name}} run: gh release upload "$tag_name" "$asset_path#$asset_name" + + - name: Attest binary provenance + id: attestation + if: steps.upload_release.outcome == 'success' + uses: actions/attest-build-provenance@v2 + with: + subject-path: ${{steps.build.outputs.path}} + subject-name: ${{steps.build.outputs.name}} + subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }} + + - name: Verify binary attestation + if: steps.attestation.outcome == 'success' + shell: bash + run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice From e7a6126fbd5839e11318beeba6ce18082b82867f Mon Sep 17 00:00:00 2001 From: justross Date: Sun, 8 Jun 2025 19:29:57 -0700 Subject: [PATCH 24/32] Web client: Updated packages with vulnerabilities using `npm audit fix` (#5955) --- webclient/package-lock.json | 1440 +++++++++++++++++++++-------------- 1 file changed, 849 insertions(+), 591 deletions(-) diff --git a/webclient/package-lock.json b/webclient/package-lock.json index fa93ceb06..b5d961a75 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -98,12 +98,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -456,17 +457,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "engines": { "node": ">=6.9.0" } @@ -494,35 +495,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.0.tgz", - "integrity": "sha512-aGMjYraN0zosCEthoGLdqot1oRsmxVTQRHadsUPz5QM44Zej2PYRz7XiDE7GqnkZnNtLbOuxqoZw42vkU7+XEQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dependencies": { + "@babel/types": "^7.27.1" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1805,36 +1795,32 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.0.tgz", - "integrity": "sha512-NDYdls71fTXoU8TZHfbBWg7DiZfNzClcKui/+kyi6ppD2L1qnWW3VV6CjtaBXSUGGhiTWJ6ereOIkUvenif66Q==", - "dependencies": { - "regenerator-runtime": "^0.13.10" - }, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.0.tgz", - "integrity": "sha512-v1JH7PeAAGBEyTQM9TqojVl+b20zXtesFKCJHu50xMxZKD1fX0TKaKHPsZfFkXfs7D1M9M6Eeqg1FkJ3a0x2dA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", + "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", "dependencies": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" + "core-js-pure": "^3.30.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1861,13 +1847,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3798,30 +3783,30 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3833,12 +3818,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@leichtgewicht/ip-codec": { @@ -4872,18 +4857,18 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, "node_modules/@types/express": { "version": "4.17.14", @@ -5009,9 +4994,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -5498,133 +5483,133 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -5656,9 +5641,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "bin": { "acorn": "bin/acorn" }, @@ -5686,14 +5671,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6488,11 +6465,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6504,9 +6481,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "funding": [ { "type": "opencollective", @@ -6515,13 +6492,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -6628,9 +6609,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001427", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001427.tgz", - "integrity": "sha512-lfXQ73oB9c8DP5Suxaszm+Ta2sr/4tf8+381GkIm1MLj/YdLf+rEDyDSRCzeltuyTVGm+/s18gdZ0q+Wmp8VsQ==", + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", "funding": [ { "type": "opencollective", @@ -6639,6 +6620,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -6976,9 +6961,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", + "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -7864,9 +7849,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==" }, "node_modules/emittery": { "version": "0.8.1", @@ -7901,9 +7886,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -8017,9 +8002,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" }, "node_modules/es-shim-unscopables": { "version": "1.0.0", @@ -8051,9 +8036,9 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -9033,6 +9018,21 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -9126,9 +9126,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -9240,9 +9240,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", @@ -9729,9 +9729,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -10060,9 +10060,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -14580,11 +14580,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -14815,9 +14815,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -15253,9 +15253,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -15411,9 +15411,9 @@ } }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -15422,12 +15422,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -16686,9 +16690,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -17658,9 +17662,9 @@ } }, "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "bin": { "rollup": "dist/bin/rollup" }, @@ -17793,18 +17797,87 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-html": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.7.3.tgz", - "integrity": "sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^6.0.0", + "htmlparser2": "^8.0.0", "is-plain-object": "^5.0.0", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, + "node_modules/sanitize-html/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/sanitize.css": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", @@ -18161,9 +18234,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -18733,12 +18806,12 @@ } }, "node_modules/terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.39.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", + "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -18750,15 +18823,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -18782,6 +18855,55 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -18820,14 +18942,6 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -19094,9 +19208,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -19105,14 +19219,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -19249,9 +19367,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -19277,33 +19395,33 @@ } }, "node_modules/webpack": { - "version": "5.76.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", - "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", + "version": "5.99.9", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", + "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -19323,9 +19441,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -19497,15 +19615,15 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", - "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -19559,10 +19677,54 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } }, "node_modules/websocket-driver": { "version": "0.7.4", @@ -20040,9 +20202,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -20162,12 +20324,13 @@ } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, "@babel/compat-data": { @@ -20423,14 +20586,14 @@ } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" }, "@babel/helper-validator-option": { "version": "7.18.6", @@ -20449,29 +20612,21 @@ } }, "@babel/helpers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.0.tgz", - "integrity": "sha512-aGMjYraN0zosCEthoGLdqot1oRsmxVTQRHadsUPz5QM44Zej2PYRz7XiDE7GqnkZnNtLbOuxqoZw42vkU7+XEQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0" - } - }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "requires": { + "@babel/types": "^7.27.1" + } }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -21292,30 +21447,26 @@ } }, "@babel/runtime": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.0.tgz", - "integrity": "sha512-NDYdls71fTXoU8TZHfbBWg7DiZfNzClcKui/+kyi6ppD2L1qnWW3VV6CjtaBXSUGGhiTWJ6ereOIkUvenif66Q==", - "requires": { - "regenerator-runtime": "^0.13.10" - } + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" }, "@babel/runtime-corejs3": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.0.tgz", - "integrity": "sha512-v1JH7PeAAGBEyTQM9TqojVl+b20zXtesFKCJHu50xMxZKD1fX0TKaKHPsZfFkXfs7D1M9M6Eeqg1FkJ3a0x2dA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", + "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", "requires": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" + "core-js-pure": "^3.30.2" } }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, "@babel/traverse": { @@ -21336,13 +21487,12 @@ } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" } }, "@bcoe/v8-coverage": { @@ -22733,27 +22883,27 @@ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } } } @@ -22764,12 +22914,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@leichtgewicht/ip-codec": { @@ -23432,18 +23582,18 @@ } }, "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, "@types/express": { "version": "4.17.14", @@ -23562,9 +23712,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "@types/json5": { "version": "0.0.29", @@ -23940,133 +24090,133 @@ } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -24095,9 +24245,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" }, "acorn-globals": { "version": "6.0.0", @@ -24115,11 +24265,6 @@ } } }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==" - }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -24711,11 +24856,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -24724,14 +24869,14 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" } }, "bser": { @@ -24805,9 +24950,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001427", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001427.tgz", - "integrity": "sha512-lfXQ73oB9c8DP5Suxaszm+Ta2sr/4tf8+381GkIm1MLj/YdLf+rEDyDSRCzeltuyTVGm+/s18gdZ0q+Wmp8VsQ==" + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -25071,9 +25216,9 @@ } }, "core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==" + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", + "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==" }, "core-util-is": { "version": "1.0.3", @@ -25698,9 +25843,9 @@ } }, "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==" }, "emittery": { "version": "0.8.1", @@ -25723,9 +25868,9 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -25818,9 +25963,9 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" }, "es-shim-unscopables": { "version": "1.0.0", @@ -25846,9 +25991,9 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -26562,6 +26707,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -26635,9 +26785,9 @@ "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -26723,9 +26873,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" }, "for-each": { "version": "0.3.3", @@ -27046,9 +27196,9 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "grapheme-splitter": { "version": "1.0.4", @@ -27301,9 +27451,9 @@ } }, "http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "requires": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -30641,11 +30791,11 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -30803,9 +30953,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "normalize-path": { "version": "3.0.0", @@ -31109,9 +31259,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -31220,13 +31370,13 @@ } }, "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, "postcss-attribute-case-insensitive": { @@ -31948,9 +32098,9 @@ } }, "protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -32615,9 +32765,9 @@ } }, "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "requires": { "fsevents": "~2.3.2" } @@ -32703,16 +32853,62 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-html": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.7.3.tgz", - "integrity": "sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", "requires": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^6.0.0", + "htmlparser2": "^8.0.0", "is-plain-object": "^5.0.0", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" + }, + "dependencies": { + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + } } }, "sanitize.css": { @@ -32987,9 +33183,9 @@ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, "source-map-loader": { "version": "3.0.2", @@ -33423,12 +33619,12 @@ } }, "terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.39.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", + "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -33441,15 +33637,52 @@ } }, "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "requires": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } } }, "test-exclude": { @@ -33482,11 +33715,6 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -33682,12 +33910,12 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, "uri-js": { @@ -33795,9 +34023,9 @@ } }, "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -33817,47 +34045,77 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "5.76.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", - "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", + "version": "5.99.9", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", + "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "dependencies": { - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } } } }, "webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "requires": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -33975,9 +34233,9 @@ } }, "ws": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", - "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==" + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==" } } }, @@ -34425,9 +34683,9 @@ } }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==" }, "xml-name-validator": { "version": "3.0.0", From 456da93465e8477e62660726606e0e373f6041b5 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:30:43 -0700 Subject: [PATCH 25/32] Support regex in card search (#5971) --- cockatrice/resources/help/search.md | 5 +++ cockatrice/src/game/filters/filter_string.cpp | 37 +++++++++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md index 917f9482e..7d707b0d1 100644 --- a/cockatrice/resources/help/search.md +++ b/cockatrice/resources/help/search.md @@ -58,4 +58,9 @@ In this list of examples below, each entry has an explanation and can be clicked
Grouping:
t:angel -(angel or c:w) (Any angel that doesn't have angel in its name and isn't white)
+
Regular Expression:
+
[/^fell/](#/^fell/) (Any card name that begins with "fell")
+
[o:/counter target .* spell/](#o:/counter target .* spell/) (Any card text with "counter target *something* spell")
+
[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) (/'s can be escaped with a \)
+ diff --git a/cockatrice/src/game/filters/filter_string.cpp b/cockatrice/src/game/filters/filter_string.cpp index e99805541..f5e9d69e9 100644 --- a/cockatrice/src/game/filters/filter_string.cpp +++ b/cockatrice/src/game/filters/filter_string.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -20,7 +21,7 @@ QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQ NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart SetQuery <- ('e'/'set') [:] FlexStringValue -OracleQuery <- 'o' [:] RegexString +OracleQuery <- 'o' [:] MatcherString CMCQuery <- ('cmc'/'mv') ws? NumericExpression @@ -42,7 +43,7 @@ ColorEx <- Color / [mc] ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx* -FieldQuery <- String [:] RegexString / String ws? NumericExpression +FieldQuery <- String [:] MatcherString / String ws? NumericExpression NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. @@ -52,8 +53,14 @@ String <- SingleApostropheString / UnescapedStringListPart+ / ["] StringMatcher { + search["NormalMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher { auto target = std::any_cast(sv[0]); - return [=](const QString &s) { - auto sanitizedTarget = QString(target); - sanitizedTarget.replace("\\\"", "\""); - sanitizedTarget.replace("\\'", "'"); - return s.contains(sanitizedTarget, Qt::CaseInsensitive); - }; + auto sanitizedTarget = QString(target); + sanitizedTarget.replace("\\\"", "\""); + sanitizedTarget.replace("\\'", "'"); + return [=](const QString &s) { return s.contains(sanitizedTarget, Qt::CaseInsensitive); }; + }; + + search["RegexMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher { + auto target = std::any_cast(sv[0]); + auto regex = QRegularExpression(target, QRegularExpression::CaseInsensitiveOption); + return [=](const QString &s) { return regex.match(s).hasMatch(); }; + }; + + search["RegexMatcherString"] = [](const peg::SemanticValues &sv) -> QString { + return QString::fromStdString(sv.token_to_string()); }; search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter { From 7495d2dc65acd3ef6e43c9303f88d29a8f3cdaa3 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:32:24 -0700 Subject: [PATCH 26/32] [VDS] Use display name for generic search expression (#5976) --- cockatrice/resources/help/deck_search.md | 6 +++--- .../deck_preview/deck_preview_widget.cpp | 12 ++++++++++-- .../deck_preview/deck_preview_widget.h | 1 + cockatrice/src/game/filters/deck_filter_string.cpp | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index cb06a40a6..e2fa4fe37 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -4,9 +4,9 @@ The search bar recognizes a set of special commands.
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
-
Filename:
-
[red deck wins](#red deck wins) (Any deck filename containing the words red, deck, and wins)
-
["red deck wins"](#%22red deck wins%22) (Any deck filename containing the exact phrase "red deck wins")
+
Display Name (The deck name, or the filename if the deck name isn't set):
+
[red deck wins](#red deck wins) (Any deck with a display name containing the words red, deck, and wins)
+
["red deck wins"](#%22red deck wins%22) (Any deck with a display name containing the exact phrase "red deck wins")
Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
[[plains]] (Any deck that contains at least one card with "plains" in its name)
diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 7fc5c729f..cce08ef8f 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -182,6 +182,15 @@ QString DeckPreviewWidget::getColorIdentity() return colorIdentity; } +/** + * The display name is given by the deck name, or the filename if the deck name is not set. + */ +QString DeckPreviewWidget::getDisplayName() const +{ + return deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() + : deckLoader->getName(); +} + void DeckPreviewWidget::setFilePath(const QString &_filePath) { filePath = _filePath; @@ -193,8 +202,7 @@ void DeckPreviewWidget::setFilePath(const QString &_filePath) */ void DeckPreviewWidget::refreshBannerCardText() { - bannerCardDisplayWidget->setOverlayText( - deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName()); + bannerCardDisplayWidget->setOverlayText(getDisplayName()); refreshBannerCardToolTip(); } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 7c0ff1ad4..c6c6d377d 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -27,6 +27,7 @@ public: const QString &_filePath); void retranslateUi(); QString getColorIdentity(); + QString getDisplayName() const; VisualDeckStorageWidget *visualDeckStorageWidget; QVBoxLayout *layout; diff --git a/cockatrice/src/game/filters/deck_filter_string.cpp b/cockatrice/src/game/filters/deck_filter_string.cpp index 52aa15c71..13c39910d 100644 --- a/cockatrice/src/game/filters/deck_filter_string.cpp +++ b/cockatrice/src/game/filters/deck_filter_string.cpp @@ -131,8 +131,8 @@ static void setupParserRules() search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); - return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) { - return info.fileSearchName.contains(name, Qt::CaseInsensitive); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { + return deck->getDisplayName().contains(name, Qt::CaseInsensitive); }; }; } From 9af3fbc35f4c4c5df75785be44e57527067124c3 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 13 Jun 2025 01:37:57 +0200 Subject: [PATCH 27/32] Propagate display type from visual deck editor to card group display widget correctly on initialization. (#5978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas Brübach --- .../widgets/cards/deck_card_zone_display_widget.cpp | 11 ++++++----- .../ui/widgets/cards/deck_card_zone_display_widget.h | 6 ++++-- .../visual_deck_editor/visual_deck_editor_widget.cpp | 10 +++++----- .../visual_deck_editor/visual_deck_editor_widget.h | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp index c5da91a2a..f1885e11f 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp @@ -12,12 +12,13 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, QString _zoneName, QString _activeGroupCriteria, QStringList _activeSortCriteria, + DisplayType _displayType, int bannerOpacity, int subBannerOpacity, CardSizeWidget *_cardSizeWidget) : QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria), - activeSortCriteria(_activeSortCriteria), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), - cardSizeWidget(_cardSizeWidget) + activeSortCriteria(_activeSortCriteria), displayType(_displayType), bannerOpacity(bannerOpacity), + subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget) { layout = new QVBoxLayout(this); setLayout(layout); @@ -60,7 +61,7 @@ void DeckCardZoneDisplayWidget::displayCards() deleteCardGroupIfItDoesNotExist(); } -void DeckCardZoneDisplayWidget::refreshDisplayType(const QString &_displayType) +void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType) { displayType = _displayType; QLayoutItem *item; @@ -102,7 +103,7 @@ void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist() continue; } - if (displayType == "overlap") { + if (displayType == DisplayType::Overlap) { auto *display_widget = new OverlappedCardGroupDisplayWidget( cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget); @@ -112,7 +113,7 @@ void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist() connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget, &CardGroupDisplayWidget::onActiveSortCriteriaChanged); cardGroupLayout->addWidget(display_widget); - } else if (displayType == "flat") { + } else if (displayType == DisplayType::Flat) { auto *display_widget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget); diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h index b80ca4332..861896abf 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h @@ -5,6 +5,7 @@ #include "../../../../game/cards/card_info.h" #include "../general/display/banner_widget.h" #include "../general/layout_containers/overlap_widget.h" +#include "../visual_deck_editor/visual_deck_editor_widget.h" #include "card_info_picture_with_text_overlay_widget.h" #include "card_size_widget.h" @@ -21,6 +22,7 @@ public: QString zoneName, QString activeGroupCriteria, QStringList activeSortCriteria, + DisplayType displayType, int bannerOpacity, int subBannerOpacity, CardSizeWidget *_cardSizeWidget); @@ -33,7 +35,7 @@ public slots: void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void onHover(CardInfoPtr card); void displayCards(); - void refreshDisplayType(const QString &displayType); + void refreshDisplayType(const DisplayType &displayType); void addCardGroupIfItDoesNotExist(); void deleteCardGroupIfItDoesNotExist(); void onActiveGroupCriteriaChanged(QString activeGroupCriteria); @@ -48,6 +50,7 @@ signals: private: QString activeGroupCriteria; QStringList activeSortCriteria; + DisplayType displayType = DisplayType::Overlap; int bannerOpacity = 20; int subBannerOpacity = 10; CardSizeWidget *cardSizeWidget; @@ -55,7 +58,6 @@ private: BannerWidget *banner; QWidget *cardGroupContainer; QVBoxLayout *cardGroupLayout; - QString displayType = "flat"; OverlapWidget *overlapWidget; }; diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 1ebf59e36..f659fac39 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -193,7 +193,7 @@ void VisualDeckEditorWidget::retranslateUi() searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setText(tr("Flat Layout")); + displayTypeButton->setText(tr("Overlap Layout")); displayTypeButton->setToolTip( tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } @@ -212,14 +212,13 @@ void VisualDeckEditorWidget::updateDisplayType() // Update UI and emit signal switch (currentDisplayType) { case DisplayType::Flat: - emit displayTypeChanged("flat"); displayTypeButton->setText(tr("Flat Layout")); break; case DisplayType::Overlap: - emit displayTypeChanged("overlap"); displayTypeButton->setText(tr("Overlap Layout")); break; } + emit displayTypeChanged(currentDisplayType); } void VisualDeckEditorWidget::addZoneIfDoesNotExist() @@ -239,8 +238,9 @@ void VisualDeckEditorWidget::addZoneIfDoesNotExist() if (found) { continue; } - DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, 20, 10, cardSizeWidget); + DeckCardZoneDisplayWidget *zoneDisplayWidget = + new DeckCardZoneDisplayWidget(zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, + currentDisplayType, 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h index 3b644ff1e..41f8bbbbe 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -50,7 +50,7 @@ signals: void activeSortCriteriaChanged(QStringList activeSortCriteria); void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); void cardAdditionRequested(CardInfoPtr card); - void displayTypeChanged(QString displayType); + void displayTypeChanged(DisplayType displayType); protected slots: void onHover(CardInfoPtr hoveredCard); From fe57efb1a81ace8315a4bc20d6b8a7498c796326 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 13 Jun 2025 01:44:38 +0200 Subject: [PATCH 28/32] Parallel picture loader (#5977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Parallelize picture loader. * Queue requests instead. * Include a status bar for the picture loader. * Save redirect cache on destruction. * Address comments. * Let's not overwrite an ambigious variable name. * Lint. * Oracle needs the status bar too. * We actually get a free request if we hit a cached image. * Fix cmake list. * toString() the url. --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 3 + .../ui/picture_loader/picture_loader.cpp | 12 + .../client/ui/picture_loader/picture_loader.h | 2 + ...e_loader_request_status_display_widget.cpp | 32 ++ ...ure_loader_request_status_display_widget.h | 66 +++ .../picture_loader_status_bar.cpp | 58 +++ .../picture_loader_status_bar.h | 29 ++ .../picture_loader/picture_loader_worker.cpp | 403 +++--------------- .../ui/picture_loader/picture_loader_worker.h | 35 +- .../picture_loader_worker_work.cpp | 236 ++++++++++ .../picture_loader_worker_work.h | 50 +++ .../quick_settings/settings_button_widget.cpp | 5 + .../quick_settings/settings_button_widget.h | 3 + .../quick_settings/settings_popup_widget.cpp | 6 + .../quick_settings/settings_popup_widget.h | 1 + oracle/CMakeLists.txt | 5 + 16 files changed, 596 insertions(+), 350 deletions(-) create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.h create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.cpp create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.h create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.cpp create mode 100644 cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index c25b319db..846167d93 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -67,7 +67,10 @@ set(cockatrice_SOURCES src/client/ui/line_edit_completer.cpp src/client/ui/phases_toolbar.cpp src/client/ui/picture_loader/picture_loader.cpp + src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp + src/client/ui/picture_loader/picture_loader_status_bar.cpp src/client/ui/picture_loader/picture_loader_worker.cpp + src/client/ui/picture_loader/picture_loader_worker_work.cpp src/client/ui/picture_loader/picture_to_load.cpp src/client/ui/pixel_map_generator.cpp src/client/ui/theme_manager.cpp diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp index 61ea8d0ad..f0a0113fe 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader.cpp +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp @@ -7,12 +7,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -27,6 +29,16 @@ PictureLoader::PictureLoader() : QObject(nullptr) connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, &PictureLoader::picDownloadChanged); connect(worker, &PictureLoaderWorker::imageLoaded, this, &PictureLoader::imageLoaded); + + statusBar = new PictureLoaderStatusBar(nullptr); + QMainWindow *mainWindow = qobject_cast(QApplication::activeWindow()); + if (mainWindow) { + mainWindow->statusBar()->addPermanentWidget(statusBar); + } + + connect(worker, &PictureLoaderWorker::imageLoadQueued, statusBar, &PictureLoaderStatusBar::addQueuedImageLoad); + connect(worker, &PictureLoaderWorker::imageLoadSuccessful, statusBar, + &PictureLoaderStatusBar::addSuccessfulImageLoad); } PictureLoader::~PictureLoader() diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.h b/cockatrice/src/client/ui/picture_loader/picture_loader.h index e2769f567..87ebfa862 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader.h +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.h @@ -2,6 +2,7 @@ #define PICTURELOADER_H #include "../../../game/cards/card_info.h" +#include "picture_loader_status_bar.h" #include "picture_loader_worker.h" #include @@ -27,6 +28,7 @@ private: void operator=(PictureLoader const &); PictureLoaderWorker *worker; + PictureLoaderStatusBar *statusBar; public: static void getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size); diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp new file mode 100644 index 000000000..3647eb71e --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp @@ -0,0 +1,32 @@ +#include "picture_loader_request_status_display_widget.h" + +PictureLoaderRequestStatusDisplayWidget::PictureLoaderRequestStatusDisplayWidget(QWidget *parent, + const QUrl &_url, + PictureLoaderWorkerWork *worker) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + + if (worker->cardToDownload.getCard()) { + name = new QLabel(this); + name->setText(worker->cardToDownload.getCard()->getName()); + setShortname = new QLabel(this); + setShortname->setText(worker->cardToDownload.getSetName()); + providerId = new QLabel(this); + providerId->setText(worker->cardToDownload.getCard()->getProperty("uuid")); + + layout->addWidget(name); + layout->addWidget(setShortname); + layout->addWidget(providerId); + } + + startTime = new QLabel(QDateTime::currentDateTime().toString(), this); + elapsedTime = new QLabel("0", this); + finished = new QLabel("False", this); + url = new QLabel(_url.toString(), this); + + layout->addWidget(startTime); + layout->addWidget(elapsedTime); + layout->addWidget(finished); + layout->addWidget(url); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.h b/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.h new file mode 100644 index 000000000..01eee1b48 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.h @@ -0,0 +1,66 @@ +#ifndef PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H +#define PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H +#include "picture_loader_worker_work.h" + +#include +#include +#include + +class PictureLoaderRequestStatusDisplayWidget : public QWidget +{ + Q_OBJECT +public: + PictureLoaderRequestStatusDisplayWidget(QWidget *parent, const QUrl &url, PictureLoaderWorkerWork *worker); + PictureLoaderWorkerWork *worker; + + void setFinished() + { + finished->setText("True"); + update(); + repaint(); + } + + bool getFinished() const + { + return finished->text() == "True"; + } + + void setElapsedTime(const QString &_elapsedTime) const + { + elapsedTime->setText(_elapsedTime); + } + + int queryElapsedSeconds() + { + if (!finished) { + int elapsedSeconds = QDateTime::fromString(startTime->text()).secsTo(QDateTime::currentDateTime()); + elapsedTime->setText(QString::number(elapsedSeconds)); + update(); + repaint(); + return elapsedSeconds; + } + return elapsedTime->text().toInt(); + } + + QString getStartTime() const + { + return startTime->text(); + } + + QString getUrl() const + { + return url->text(); + } + +private: + QHBoxLayout *layout; + QLabel *name; + QLabel *setShortname; + QLabel *providerId; + QLabel *startTime; + QLabel *elapsedTime; + QLabel *finished; + QLabel *url; +}; + +#endif // PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.cpp new file mode 100644 index 000000000..67b50a1f2 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.cpp @@ -0,0 +1,58 @@ +#include "picture_loader_status_bar.h" + +#include "picture_loader_request_status_display_widget.h" + +PictureLoaderStatusBar::PictureLoaderStatusBar(QWidget *parent) : QWidget(parent) +{ + layout = new QHBoxLayout(this); + progressBar = new QProgressBar(this); + progressBar->setMaximum(0); + progressBar->setFormat("%v/%m"); + layout->addWidget(progressBar); + + loadLog = new SettingsButtonWidget(this); + layout->addWidget(loadLog); + + cleaner = new QTimer(this); + cleaner->setInterval(1000); + connect(cleaner, &QTimer::timeout, this, &PictureLoaderStatusBar::cleanOldEntries); + cleaner->start(); + + setLayout(layout); +} + +void PictureLoaderStatusBar::cleanOldEntries() +{ + if (!loadLog || !loadLog->popup) { + return; + } + for (PictureLoaderRequestStatusDisplayWidget *statusDisplayWidget : + loadLog->popup->findChildren()) { + statusDisplayWidget->queryElapsedSeconds(); + if (statusDisplayWidget->getFinished() && + QDateTime::fromString(statusDisplayWidget->getStartTime()).secsTo(QDateTime::currentDateTime()) > 10) { + loadLog->removeSettingsWidget(statusDisplayWidget); + progressBar->setMaximum(progressBar->maximum() - 1); + progressBar->setValue(progressBar->value() - 1); + } + } +} + +void PictureLoaderStatusBar::addQueuedImageLoad(const QUrl &url, PictureLoaderWorkerWork *worker) +{ + loadLog->addSettingsWidget(new PictureLoaderRequestStatusDisplayWidget(loadLog, url, worker)); + progressBar->setMaximum(progressBar->maximum() + 1); +} + +void PictureLoaderStatusBar::addSuccessfulImageLoad(const QUrl &url, PictureLoaderWorkerWork *worker) +{ + Q_UNUSED(worker) + progressBar->setValue(progressBar->value() + 1); + for (PictureLoaderRequestStatusDisplayWidget *statusDisplayWidget : + loadLog->popup->findChildren()) { + if (statusDisplayWidget->getUrl() == url.toString()) { + statusDisplayWidget->queryElapsedSeconds(); + statusDisplayWidget->setFinished(); + } + } +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.h b/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.h new file mode 100644 index 000000000..6a507cbb0 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.h @@ -0,0 +1,29 @@ +#ifndef PICTURE_LOADER_STATUS_BAR_H +#define PICTURE_LOADER_STATUS_BAR_H + +#include "../widgets/quick_settings/settings_button_widget.h" +#include "picture_loader_worker_work.h" + +#include +#include +#include + +class PictureLoaderStatusBar : public QWidget +{ + Q_OBJECT +public: + explicit PictureLoaderStatusBar(QWidget *parent); + +public slots: + void addQueuedImageLoad(const QUrl &url, PictureLoaderWorkerWork *worker); + void addSuccessfulImageLoad(const QUrl &url, PictureLoaderWorkerWork *worker); + void cleanOldEntries(); + +private: + QHBoxLayout *layout; + QProgressBar *progressBar; + SettingsButtonWidget *loadLog; + QTimer *cleaner; +}; + +#endif // PICTURE_LOADER_STATUS_BAR_H diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp index fce81fe6a..5829539cb 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp @@ -2,13 +2,15 @@ #include "../../../game/cards/card_database_manager.h" #include "../../../settings/cache_settings.h" +#include "picture_loader_worker_work.h" -#include #include +#include #include #include #include #include +#include // Card back returned by gatherer when card is not found QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; @@ -19,11 +21,8 @@ PictureLoaderWorker::PictureLoaderWorker() picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false), overrideAllCardArtWithPersonalPreference(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - connect(this, &PictureLoaderWorker::startLoadQueue, this, &PictureLoaderWorker::processLoadQueue, - Qt::QueuedConnection); - connect(&SettingsCache::instance(), &SettingsCache::picsPathChanged, this, &PictureLoaderWorker::picsPathChanged); - connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, - &PictureLoaderWorker::picDownloadChanged); + connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); + connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, &PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference); @@ -34,18 +33,17 @@ PictureLoaderWorker::PictureLoaderWorker() #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); #endif - auto cache = new QNetworkDiskCache(this); + cache = new QNetworkDiskCache(this); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); cache->setMaximumCacheSize(1024L * 1024L * static_cast(SettingsCache::instance().getNetworkCacheSizeInMB())); // Note: the settings is in MB, but QNetworkDiskCache uses bytes - connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, - [cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); + connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, this, + [this](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); networkManager->setCache(cache); // Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished // We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); - connect(networkManager, &QNetworkAccessManager::finished, this, &PictureLoaderWorker::picDownloadFinished); cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME; loadRedirectCache(); @@ -57,244 +55,104 @@ PictureLoaderWorker::PictureLoaderWorker() pictureLoaderThread = new QThread; pictureLoaderThread->start(QThread::LowPriority); moveToThread(pictureLoaderThread); + + connect(&requestTimer, &QTimer::timeout, this, &PictureLoaderWorker::processQueuedRequests); + requestTimer.setInterval(1000); + requestTimer.start(); } PictureLoaderWorker::~PictureLoaderWorker() { + saveRedirectCache(); pictureLoaderThread->deleteLater(); } -void PictureLoaderWorker::processLoadQueue() +void PictureLoaderWorker::queueRequest(const QUrl &url, PictureLoaderWorkerWork *worker) { - if (loadQueueRunning) { - return; - } - - loadQueueRunning = true; - while (true) { - mutex.lock(); - if (loadQueue.isEmpty()) { - mutex.unlock(); - loadQueueRunning = false; - return; - } - cardBeingLoaded = loadQueue.takeFirst(); - mutex.unlock(); - - QString setName = cardBeingLoaded.getSetName(); - QString cardName = cardBeingLoaded.getCard()->getName(); - QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); - - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardName << " set: " << setName << "]: Trying to load picture"; - - // FIXME: This is a hack so that to keep old Cockatrice behavior - // (ignoring provider ID) when the "override all card art with personal - // preference" is set. - // - // Figure out a proper way to integrate the two systems at some point. - // - // Note: need to go through a member for - // overrideAllCardArtWithPersonalPreference as reading from the - // SettingsCache instance from the PictureLoaderWorker thread could - // cause race conditions. - // - // XXX: Reading from the CardDatabaseManager instance from the - // PictureLoaderWorker thread might not be safe either - bool searchCustomPics = overrideAllCardArtWithPersonalPreference || - CardDatabaseManager::getInstance()->isProviderIdForPreferredPrinting( - cardName, cardBeingLoaded.getCard()->getPixmapCacheKey()); - if (searchCustomPics && cardImageExistsOnDisk(setName, correctedCardName, searchCustomPics)) { - continue; - } - - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardName << " set: " << setName << "]: No custom picture, trying to download"; - cardsToDownload.append(cardBeingLoaded); - cardBeingLoaded.clear(); - if (!downloadRunning) { - startNextPicDownload(); - } - } -} - -bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname, bool searchCustomPics) -{ - QImage image; - QImageReader imgReader; - imgReader.setDecideFormatFromContent(true); - QList picsPaths = QList(); - - if (searchCustomPics) { - QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - - // Recursively check all subdirectories of the CUSTOM folder - while (it.hasNext()) { - QString thisPath(it.next()); - QFileInfo thisFileInfo(thisPath); - - if (thisFileInfo.isFile() && - (thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname || - thisFileInfo.baseName() == correctedCardname)) { - picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere - } - } - } - - if (!setName.isEmpty()) { - picsPaths << picsPath + "/" + setName + "/" + correctedCardname - // We no longer store downloaded images there, but don't just ignore - // stuff that old versions have put there. - << picsPath + "/downloadedPics/" + setName + "/" + correctedCardname; - } - - // Iterates through the list of paths, searching for images with the desired - // name with any QImageReader-supported - // extension - for (const auto &_picsPath : picsPaths) { - imgReader.setFileName(_picsPath); - if (imgReader.read(&image)) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << correctedCardname << " set: " << setName << "]: Picture found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - imgReader.setFileName(_picsPath + ".full"); - if (imgReader.read(&image)) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << correctedCardname << " set: " << setName << "]: Picture.full found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - imgReader.setFileName(_picsPath + ".xlhq"); - if (imgReader.read(&image)) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << correctedCardname << " set: " << setName << "]: Picture.xlhq found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - } - - return false; -} - -void PictureLoaderWorker::startNextPicDownload() -{ - if (cardsToDownload.isEmpty()) { - cardBeingDownloaded.clear(); - downloadRunning = false; - return; - } - - downloadRunning = true; - - cardBeingDownloaded = cardsToDownload.takeFirst(); - - QString picUrl = cardBeingDownloaded.getCurrentUrl(); - - if (picUrl.isEmpty()) { - downloadRunning = false; - picDownloadFailed(); - } else { - QUrl url(picUrl); - qCDebug(PictureLoaderWorkerLog).nospace() << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() - << "]: Trying to fetch picture from url " << url.toDisplayString(); - makeRequest(url); - } -} - -void PictureLoaderWorker::picDownloadFailed() -{ - /* Take advantage of short circuiting here to call the nextUrl until one - is not available. Only once nextUrl evaluates to false will this move - on to nextSet. If the Urls for a particular card are empty, this will - effectively go through the sets for that card. */ - if (cardBeingDownloaded.nextUrl() || cardBeingDownloaded.nextSet()) { - mutex.lock(); - loadQueue.prepend(cardBeingDownloaded); - mutex.unlock(); - } else { - qCWarning(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, " - << (picDownload ? "download failed" : "downloads disabled") - << ", no more url combinations to try: BAILING OUT"; - imageLoaded(cardBeingDownloaded.getCard(), QImage()); - cardBeingDownloaded.clear(); - } - emit startLoadQueue(); -} - -bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData) -{ - QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex(); - return md5Blacklist.contains(md5sum); -} - -QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) -{ - // Check if the redirect is cached QUrl cachedRedirect = getCachedRedirect(url); if (!cachedRedirect.isEmpty()) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Using cached redirect for " << url.toDisplayString() - << " to " << cachedRedirect.toDisplayString(); - return makeRequest(cachedRedirect); // Use the cached redirect + queueRequest(cachedRedirect, worker); + } + if (cache->metaData(url).isValid()) { + makeRequest(url, worker); + } else { + requestLoadQueue.append(qMakePair(url, worker)); + emit imageLoadQueued(url, worker); + } +} + +QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url, PictureLoaderWorkerWork *worker) +{ + // Check for cached redirects + QUrl cachedRedirect = getCachedRedirect(url); + if (!cachedRedirect.isEmpty()) { + emit imageLoadSuccessful(url, worker); + return makeRequest(cachedRedirect, worker); } QNetworkRequest req(url); - - // QNetworkDiskCache leaks file descriptors when downloading compressed - // files, see https://bugreports.qt.io/browse/QTBUG-135641 - // - // We can set the Accept-Encoding header manually to disable Qt's automatic - // response decompression, but then we would have to deal with decompression - // ourselves. - // - // Since we are dowloading images that are usually stored in a - // compressed format (e.g. jpeg or webp), it's not clear compression at the - // HTTP level helps; in fact, images are typically returned without - // compression. Redirects, on the other hand, are compressed and cause file - // descriptor leaks -- but since redirects have no payload, we don't really - // care either. - // - // In the end, just do the simple thing and disable HTTP compression. - req.setRawHeader("accept-encoding", "identity"); - if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } QNetworkReply *reply = networkManager->get(req); - connect(reply, &QNetworkReply::finished, this, [this, reply, url]() { + // Connect reply handling + connect(reply, &QNetworkReply::finished, this, [this, reply, url, worker]() { QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - if (redirectTarget.isValid()) { QUrl redirectUrl = redirectTarget.toUrl(); if (redirectUrl.isRelative()) { redirectUrl = url.resolved(redirectUrl); } - cacheRedirect(url, redirectUrl); - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Caching redirect from " << url.toDisplayString() - << " to " << redirectUrl.toDisplayString(); } + if (reply->error() == QNetworkReply::NoError) { + worker->picDownloadFinished(reply); + emit imageLoadSuccessful(url, worker); + + // If we hit a cached image, we get to make another request for free. + if (reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { + if (!requestLoadQueue.isEmpty()) { + auto request = requestLoadQueue.takeFirst(); + makeRequest(request.first, request.second); + } + } + } else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 429) { + qInfo() << "Too many requests."; + } reply->deleteLater(); }); return reply; } +void PictureLoaderWorker::processQueuedRequests() +{ + for (int i = 0; i < 10; i++) { + if (!requestLoadQueue.isEmpty()) { + auto request = requestLoadQueue.takeFirst(); + makeRequest(request.first, request.second); + } + } +} + +void PictureLoaderWorker::enqueueImageLoad(const CardInfoPtr &card) +{ + auto worker = new PictureLoaderWorkerWork(this, card); + Q_UNUSED(worker); +} + +void PictureLoaderWorker::imageLoadedSuccessfully(CardInfoPtr card, const QImage &image) +{ + emit imageLoaded(std::move(card), image); +} + void PictureLoaderWorker::cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl) { redirectCache[originalUrl] = qMakePair(redirectUrl, QDateTime::currentDateTimeUtc()); - saveRedirectCache(); + // saveRedirectCache(); } QUrl PictureLoaderWorker::getCachedRedirect(const QUrl &originalUrl) const @@ -353,129 +211,6 @@ void PictureLoaderWorker::cleanStaleEntries() } } -void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) -{ - bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); - - if (reply->error()) { - if (isFromCache) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: Removing corrupted cache file for url " << reply->url().toDisplayString() << " and retrying (" - << reply->errorString() << ")"; - - networkManager->cache()->remove(reply->url()); - - makeRequest(reply->url()); - } else { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: " << (picDownload ? "Download" : "Cache search") << " failed for url " - << reply->url().toDisplayString() << " (" << reply->errorString() << ")"; - - picDownloadFailed(); - startNextPicDownload(); - } - - reply->deleteLater(); - return; - } - - // List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || - statusCode == 308) { - QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl(); - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: following " << (isFromCache ? "cached redirect" : "redirect") << " to " - << redirectUrl.toDisplayString(); - makeRequest(redirectUrl); - reply->deleteLater(); - return; - } - - // peek is used to keep the data in the buffer for use by QImageReader - const QByteArray &picData = reply->peek(reply->size()); - - if (imageIsBlackListed(picData)) { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: Picture found, but blacklisted, will consider it as not found"; - - picDownloadFailed(); - reply->deleteLater(); - startNextPicDownload(); - return; - } - - QImage testImage; - - QImageReader imgReader; - imgReader.setDecideFormatFromContent(true); - imgReader.setDevice(reply); - - bool logSuccessMessage = false; - - static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h - auto replyHeader = reply->peek(riffHeaderSize); - - if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) { - auto imgBuf = QBuffer(this); - imgBuf.setData(reply->readAll()); - - auto movie = QMovie(&imgBuf); - movie.start(); - movie.stop(); - - imageLoaded(cardBeingDownloaded.getCard(), movie.currentImage()); - logSuccessMessage = true; - } else if (imgReader.read(&testImage)) { - imageLoaded(cardBeingDownloaded.getCard(), testImage); - logSuccessMessage = true; - } else { - qCDebug(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: Possible " << (isFromCache ? "cached" : "downloaded") << " picture at " - << reply->url().toDisplayString() << " could not be loaded: " << reply->errorString(); - - picDownloadFailed(); - } - - if (logSuccessMessage) { - qCInfo(PictureLoaderWorkerLog).nospace() - << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() - << "]: Image successfully " << (isFromCache ? "loaded from cached" : "downloaded from") << " url " - << reply->url().toDisplayString(); - } - - reply->deleteLater(); - startNextPicDownload(); -} - -void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card) -{ - QMutexLocker locker(&mutex); - - // avoid queueing the same card more than once - if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) { - return; - } - - for (const PictureToLoad &pic : loadQueue) { - if (pic.getCard() == card) - return; - } - - for (const PictureToLoad &pic : cardsToDownload) { - if (pic.getCard() == card) - return; - } - - loadQueue.append(PictureToLoad(card)); - emit startLoadQueue(); -} - void PictureLoaderWorker::picDownloadChanged() { QMutexLocker locker(&mutex); diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h index 47d2cca5a..2e0735dbb 100644 --- a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h @@ -1,13 +1,18 @@ #ifndef PICTURE_LOADER_WORKER_H #define PICTURE_LOADER_WORKER_H +#include "../../../game/cards/card_database.h" #include "../../../game/cards/card_info.h" +#include "picture_loader_worker_work.h" #include "picture_to_load.h" #include #include #include +#include #include +#include +#include #define REDIRECT_HEADER_NAME "redirects" #define REDIRECT_ORIGINAL_URL "original" @@ -17,16 +22,23 @@ inline Q_LOGGING_CATEGORY(PictureLoaderWorkerLog, "picture_loader.worker"); +class PictureLoaderWorkerWork; class PictureLoaderWorker : public QObject { Q_OBJECT public: explicit PictureLoaderWorker(); ~PictureLoaderWorker() override; + void queueRequest(const QUrl &url, PictureLoaderWorkerWork *worker); - void enqueueImageLoad(CardInfoPtr card); + void enqueueImageLoad(const CardInfoPtr &card); void clearNetworkCache(); +public slots: + QNetworkReply *makeRequest(const QUrl &url, PictureLoaderWorkerWork *workThread); + void processQueuedRequests(); + void imageLoadedSuccessfully(CardInfoPtr card, const QImage &image); + private: static QStringList md5Blacklist; @@ -35,6 +47,7 @@ private: QList loadQueue; QMutex mutex; QNetworkAccessManager *networkManager; + QNetworkDiskCache *cache; QHash> redirectCache; // Stores redirect and timestamp QString cacheFilePath; // Path to persistent storage static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable @@ -42,18 +55,11 @@ private: PictureToLoad cardBeingLoaded; PictureToLoad cardBeingDownloaded; bool picDownload, downloadRunning, loadQueueRunning; + QQueue> requestLoadQueue; + QList> requestQueue; + QTimer requestTimer; // Timer for processing delayed requests bool overrideAllCardArtWithPersonalPreference; - void startNextPicDownload(); - /** Emit the `imageLoaded` signal and return `true` if a picture is found on - disk, return `false` otherwise. - - If `searchCustomPics` is `true`, the CUSTOM folder is searched for a - matching image first; otherwise, only the set-based folders are used. */ - bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName, bool searchCustomPics); - - bool imageIsBlackListed(const QByteArray &); - QNetworkReply *makeRequest(const QUrl &url); void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl); QUrl getCachedRedirect(const QUrl &originalUrl) const; void loadRedirectCache(); @@ -61,18 +67,15 @@ private: void cleanStaleEntries(); private slots: - void picDownloadFinished(QNetworkReply *reply); - void picDownloadFailed(); - void picDownloadChanged(); void picsPathChanged(); void setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference); -public slots: - void processLoadQueue(); signals: void startLoadQueue(); void imageLoaded(CardInfoPtr card, const QImage &image); + void imageLoadQueued(const QUrl &url, PictureLoaderWorkerWork *worker); + void imageLoadSuccessful(const QUrl &url, PictureLoaderWorkerWork *worker); }; #endif // PICTURE_LOADER_WORKER_H diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.cpp new file mode 100644 index 000000000..bc9787767 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.cpp @@ -0,0 +1,236 @@ +#include "picture_loader_worker_work.h" + +#include "../../../game/cards/card_database_manager.h" +#include "../../../settings/cache_settings.h" +#include "picture_loader_worker.h" + +#include +#include +#include +#include +#include +#include +#include + +// Card back returned by gatherer when card is not found +QStringList PictureLoaderWorkerWork::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; + +PictureLoaderWorkerWork::PictureLoaderWorkerWork(PictureLoaderWorker *_worker, const CardInfoPtr &toLoad) + : QThread(nullptr), worker(_worker), cardToDownload(toLoad) +{ + connect(this, &PictureLoaderWorkerWork::requestImageDownload, worker, &PictureLoaderWorker::queueRequest, + Qt::QueuedConnection); + connect(this, &PictureLoaderWorkerWork::imageLoaded, worker, &PictureLoaderWorker::imageLoadedSuccessfully, + Qt::QueuedConnection); + pictureLoaderThread = new QThread; + pictureLoaderThread->start(QThread::LowPriority); + moveToThread(pictureLoaderThread); + startNextPicDownload(); +} + +PictureLoaderWorkerWork::~PictureLoaderWorkerWork() +{ + pictureLoaderThread->deleteLater(); +} + +bool PictureLoaderWorkerWork::cardImageExistsOnDisk(QString &setName, QString &correctedCardname) +{ + QImage image; + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + QList picsPaths = QList(); + QDirIterator it(SettingsCache::instance().getCustomPicsPath(), + QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + // Recursively check all subdirectories of the CUSTOM folder + while (it.hasNext()) { + QString thisPath(it.next()); + QFileInfo thisFileInfo(thisPath); + + if (thisFileInfo.isFile() && + (thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname || + thisFileInfo.baseName() == correctedCardname)) { + picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere + } + } + + if (!setName.isEmpty()) { + picsPaths << SettingsCache::instance().getPicsPath() + "/" + setName + "/" + correctedCardname + // We no longer store downloaded images there, but don't just ignore + // stuff that old versions have put there. + << SettingsCache::instance().getPicsPath() + "/downloadedPics/" + setName + "/" + correctedCardname; + } + + // Iterates through the list of paths, searching for images with the desired + // name with any QImageReader-supported + // extension + for (const auto &_picsPath : picsPaths) { + imgReader.setFileName(_picsPath); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture found on disk."; + imageLoaded(cardToDownload.getCard(), image); + return true; + } + imgReader.setFileName(_picsPath + ".full"); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() << "PictureLoader: [card: " << correctedCardname + << " set: " << setName << "]: Picture.full found on disk."; + imageLoaded(cardToDownload.getCard(), image); + return true; + } + imgReader.setFileName(_picsPath + ".xlhq"); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() << "PictureLoader: [card: " << correctedCardname + << " set: " << setName << "]: Picture.xlhq found on disk."; + imageLoaded(cardToDownload.getCard(), image); + return true; + } + } + + return false; +} + +void PictureLoaderWorkerWork::startNextPicDownload() +{ + QString picUrl = cardToDownload.getCurrentUrl(); + + if (picUrl.isEmpty()) { + downloadRunning = false; + picDownloadFailed(); + } else { + QUrl url(picUrl); + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getCorrectedName() + << " set: " << cardToDownload.getSetName() << "]: Trying to fetch picture from url " + << url.toDisplayString(); + emit requestImageDownload(url, this); + } +} + +void PictureLoaderWorkerWork::picDownloadFailed() +{ + /* Take advantage of short-circuiting here to call the nextUrl until one + is not available. Only once nextUrl evaluates to false will this move + on to nextSet. If the Urls for a particular card are empty, this will + effectively go through the sets for that card. */ + if (cardToDownload.nextUrl() || cardToDownload.nextSet()) { + startNextPicDownload(); + } else { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getCorrectedName() + << " set: " << cardToDownload.getSetName() << "]: Picture NOT found, " + << (picDownload ? "download failed" : "downloads disabled") + << ", no more url combinations to try: BAILING OUT"; + imageLoaded(cardToDownload.getCard(), QImage()); + } + emit startLoadQueue(); +} + +void PictureLoaderWorkerWork::picDownloadFinished(QNetworkReply *reply) +{ + bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); + + if (reply->error()) { + if (isFromCache) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() << "]: Removing corrupted cache file for url " + << reply->url().toDisplayString() << " and retrying (" << reply->errorString() << ")"; + + networkManager->cache()->remove(reply->url()); + + requestImageDownload(reply->url(), this); + } else { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() << "]: " << (picDownload ? "Download" : "Cache search") + << " failed for url " << reply->url().toDisplayString() << " (" << reply->errorString() << ")"; + + picDownloadFailed(); + startNextPicDownload(); + } + + reply->deleteLater(); + return; + } + + // List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || + statusCode == 308) { + QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl(); + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() << "]: following " + << (isFromCache ? "cached redirect" : "redirect") << " to " << redirectUrl.toDisplayString(); + requestImageDownload(redirectUrl, this); + reply->deleteLater(); + return; + } + + // peek is used to keep the data in the buffer for use by QImageReader + const QByteArray &picData = reply->peek(reply->size()); + + if (imageIsBlackListed(picData)) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() + << "]: Picture found, but blacklisted, will consider it as not found"; + + picDownloadFailed(); + reply->deleteLater(); + startNextPicDownload(); + return; + } + + QImage testImage; + + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + imgReader.setDevice(reply); + + bool logSuccessMessage = false; + + static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h + auto replyHeader = reply->peek(riffHeaderSize); + + if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) { + auto imgBuf = QBuffer(this); + imgBuf.setData(reply->readAll()); + + auto movie = QMovie(&imgBuf); + movie.start(); + movie.stop(); + + imageLoaded(cardToDownload.getCard(), movie.currentImage()); + logSuccessMessage = true; + } else if (imgReader.read(&testImage)) { + imageLoaded(cardToDownload.getCard(), testImage); + logSuccessMessage = true; + } else { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() << "]: Possible " << (isFromCache ? "cached" : "downloaded") + << " picture at " << reply->url().toDisplayString() << " could not be loaded: " << reply->errorString(); + + picDownloadFailed(); + } + + if (logSuccessMessage) { + qCDebug(PictureLoaderWorkerWorkLog).nospace() + << "PictureLoader: [card: " << cardToDownload.getCard()->getName() + << " set: " << cardToDownload.getSetName() << "]: Image successfully " + << (isFromCache ? "loaded from cached" : "downloaded from") << " url " << reply->url().toDisplayString(); + } else { + startNextPicDownload(); + } + + reply->deleteLater(); +} + +bool PictureLoaderWorkerWork::imageIsBlackListed(const QByteArray &picData) +{ + QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex(); + return md5Blacklist.contains(md5sum); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.h b/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.h new file mode 100644 index 000000000..bbc1564ec --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.h @@ -0,0 +1,50 @@ +#ifndef PICTURE_LOADER_WORKER_WORK_H +#define PICTURE_LOADER_WORKER_WORK_H + +#include "../../../game/cards/card_database.h" +#include "picture_loader_worker.h" +#include "picture_to_load.h" + +#include +#include +#include +#include +#include + +#define REDIRECT_HEADER_NAME "redirects" +#define REDIRECT_ORIGINAL_URL "original" +#define REDIRECT_URL "redirect" +#define REDIRECT_TIMESTAMP "timestamp" +#define REDIRECT_CACHE_FILENAME "cache.ini" + +inline Q_LOGGING_CATEGORY(PictureLoaderWorkerWorkLog, "picture_loader.worker"); + +class PictureLoaderWorker; +class PictureLoaderWorkerWork : public QThread +{ + Q_OBJECT +public: + explicit PictureLoaderWorkerWork(PictureLoaderWorker *worker, const CardInfoPtr &toLoad); + ~PictureLoaderWorkerWork() override; + PictureLoaderWorker *worker; + PictureToLoad cardToDownload; +public slots: + void picDownloadFinished(QNetworkReply *reply); + void picDownloadFailed(); + +private: + static QStringList md5Blacklist; + QThread *pictureLoaderThread; + QNetworkAccessManager *networkManager; + bool picDownload, downloadRunning, loadQueueRunning; + void startNextPicDownload(); + bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName); + bool imageIsBlackListed(const QByteArray &); + +signals: + void startLoadQueue(); + void imageLoaded(CardInfoPtr card, const QImage &image); + void requestImageDownload(const QUrl &url, PictureLoaderWorkerWork *instance); +}; + +#endif // PICTURE_LOADER_WORKER_WORK_H diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp index 0818050d8..17087c8bc 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp @@ -26,6 +26,11 @@ void SettingsButtonWidget::addSettingsWidget(QWidget *toAdd) const popup->addSettingsWidget(toAdd); } +void SettingsButtonWidget::removeSettingsWidget(QWidget *toRemove) const +{ + popup->removeSettingsWidget(toRemove); +} + void SettingsButtonWidget::setButtonIcon(QPixmap iconMap) { button->setIcon(iconMap); diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h index 4234ed438..3483b7625 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h @@ -13,6 +13,7 @@ class SettingsButtonWidget : public QWidget public: explicit SettingsButtonWidget(QWidget *parent = nullptr); void addSettingsWidget(QWidget *toAdd) const; + void removeSettingsWidget(QWidget *toRemove) const; void setButtonIcon(QPixmap iconMap); protected: @@ -25,6 +26,8 @@ private slots: private: QHBoxLayout *layout; QToolButton *button; + +public: SettingsPopupWidget *popup; }; diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp index 258f1163e..9b2e55e0f 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp @@ -29,6 +29,12 @@ void SettingsPopupWidget::addSettingsWidget(QWidget *toAdd) const containerLayout->addWidget(toAdd); // Add to containerWidget's layout } +void SettingsPopupWidget::removeSettingsWidget(QWidget *toRemove) const +{ + containerLayout->removeWidget(toRemove); + toRemove->deleteLater(); +} + void SettingsPopupWidget::adjustSizeToFitScreen() { QScreen *screen = QApplication::screenAt(this->pos()); diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h index d57c123c4..6b6157b45 100644 --- a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h @@ -13,6 +13,7 @@ class SettingsPopupWidget : public QWidget public: explicit SettingsPopupWidget(QWidget *parent = nullptr); void addSettingsWidget(QWidget *toAdd) const; + void removeSettingsWidget(QWidget *toRemove) const; void adjustSizeToFitScreen(); signals: diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 130dfdaab..ba5dd7284 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -21,8 +21,13 @@ set(oracle_SOURCES ../cockatrice/src/game/cards/card_database_manager.cpp ../cockatrice/src/game/cards/card_info.cpp ../cockatrice/src/client/ui/picture_loader/picture_loader.cpp + ../cockatrice/src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp + ../cockatrice/src/client/ui/picture_loader/picture_loader_status_bar.cpp ../cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp + ../cockatrice/src/client/ui/picture_loader/picture_loader_worker_work.cpp ../cockatrice/src/client/ui/picture_loader/picture_to_load.cpp + ../cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp + ../cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp ../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp ../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp ../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp From a688a5fe72f39eca58130210c69977adfa5ba639 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 13 Jun 2025 01:47:03 +0200 Subject: [PATCH 29/32] [VDD] Filter performance (#5974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sort saved filters by name. * Optimize performance, allow loading name filters from clipboard. * Fix include. --------- Co-authored-by: Lukas Brübach --- ...tabase_display_filter_save_load_widget.cpp | 2 +- ...al_database_display_name_filter_widget.cpp | 33 ++++++++++++++++--- ...sual_database_display_name_filter_widget.h | 2 ++ .../visual_database_display_widget.cpp | 10 +++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 21ec205fd..b97032126 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -125,7 +125,7 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList() // Refresh the filter file list QDir dir(SettingsCache::instance().getFiltersPath()); - QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Time); + QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name); // Loop through the filter files and create widgets for them for (const QString &filename : filterFiles) { diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index e0ededd50..b74d49ab7 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -1,5 +1,6 @@ #include "visual_database_display_name_filter_widget.h" +#include "../../../../dialogs/dlg_load_deck_from_clipboard.h" #include "../../../tabs/abstract_tab_deck_editor.h" #include @@ -35,6 +36,13 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi layout->addWidget(loadFromDeckButton); connect(loadFromDeckButton, &QPushButton::clicked, this, &VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck); + + loadFromClipboardButton = new QPushButton(this); + layout->addWidget(loadFromClipboardButton); + + connect(loadFromClipboardButton, &QPushButton::clicked, this, + &VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel); }); @@ -46,6 +54,8 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() searchBox->setPlaceholderText(tr("Filter by name...")); loadFromDeckButton->setText(tr("Load from Deck")); loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters")); + loadFromClipboardButton->setText(tr("Load from Clipboard")); + loadFromClipboardButton->setToolTip(tr("Apply all card names in clipboard as exact match name filters")); } void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() @@ -75,6 +85,20 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() updateFilterModel(); } +void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard() +{ + DlgLoadDeckFromClipboard dlg(this); + if (!dlg.exec()) + return; + + QStringList cardsInClipboard = dlg.getDeckList()->getCardList(); + for (QString cardName : cardsInClipboard) { + createNameFilter(cardName); + } + + updateFilterModel(); +} + void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name) { if (activeFilters.contains(name)) @@ -85,7 +109,11 @@ void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" "QPushButton:hover { background-color: red; color: white; }"); - connect(button, &QPushButton::clicked, this, [this, name]() { removeNameFilter(name); }); + connect(button, &QPushButton::clicked, this, [this, name]() { + removeNameFilter(name); + QTimer::singleShot(0, this, + &VisualDatabaseDisplayNameFilterWidget::updateFilterModel); // Avoid concurrent modification + }); flowWidget->addWidget(button); activeFilters[name] = button; @@ -96,9 +124,6 @@ void VisualDatabaseDisplayNameFilterWidget::removeNameFilter(const QString &name if (activeFilters.contains(name)) { activeFilters[name]->deleteLater(); // Safe deletion activeFilters.remove(name); - - QTimer::singleShot(0, this, - &VisualDatabaseDisplayNameFilterWidget::updateFilterModel); // Avoid concurrent modification } } diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h index 91ad56160..b0989c5d7 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -35,11 +35,13 @@ private: QLineEdit *searchBox; FlowWidget *flowWidget; QPushButton *loadFromDeckButton; + QPushButton *loadFromClipboardButton; QMap activeFilters; // Store active name filter buttons private slots: void actLoadFromDeck(); + void actLoadFromClipboard(); }; #endif // VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp index f394f508f..ae0b0e34b 100644 --- a/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp @@ -95,7 +95,15 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, clearFilterWidget = new QToolButton(); clearFilterWidget->setFixedSize(32, 32); clearFilterWidget->setIcon(QPixmap("theme:icons/delete")); - connect(clearFilterWidget, &QToolButton::clicked, this, [this] { filterModel->clear(); }); + connect(clearFilterWidget, &QToolButton::clicked, this, [this] { + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clear(); + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); + }); quickFilterSaveLoadWidget = new SettingsButtonWidget(this); quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/lock")); From 7e08f7df675535ef4d3c5062ff8999cdb5176160 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:45:01 -0700 Subject: [PATCH 30/32] [VDS] Remove searchFolderName setting (#5970) * [VDS] Remove searchFolderName setting * rename property in ExtraDeckSearchInfo --- ...ual_deck_storage_quick_settings_widget.cpp | 15 -------------- ...isual_deck_storage_quick_settings_widget.h | 3 --- .../visual_deck_storage_search_widget.cpp | 20 ++++++++----------- .../visual_deck_storage_search_widget.h | 2 +- .../visual_deck_storage_widget.cpp | 5 +---- .../src/game/filters/deck_filter_string.h | 4 ++-- cockatrice/src/settings/cache_settings.cpp | 7 ------- cockatrice/src/settings/cache_settings.h | 6 ------ dbconverter/src/mocks.cpp | 3 --- tests/carddatabase/mocks.cpp | 3 --- 10 files changed, 12 insertions(+), 56 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp index cd085a511..385007c3e 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -42,14 +42,6 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg connect(showBannerCardComboBoxCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageShowBannerCardComboBox); - // search folder names checkbox - searchFolderNamesCheckBox = new QCheckBox(this); - searchFolderNamesCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageSearchFolderNames()); - connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, this, - &VisualDeckStorageQuickSettingsWidget::searchFolderNamesChanged); - connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageSearchFolderNames); - // draw unused color identities checkbox drawUnusedColorIdentitiesCheckBox = new QCheckBox(this); drawUnusedColorIdentitiesCheckBox->setChecked( @@ -113,7 +105,6 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg this->addSettingsWidget(showTagFilterCheckBox); this->addSettingsWidget(showTagsOnDeckPreviewsCheckBox); this->addSettingsWidget(showBannerCardComboBoxCheckBox); - this->addSettingsWidget(searchFolderNamesCheckBox); this->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); this->addSettingsWidget(unusedColorIdentityOpacityWidget); this->addSettingsWidget(deckPreviewTooltipWidget); @@ -130,7 +121,6 @@ void VisualDeckStorageQuickSettingsWidget::retranslateUi() showTagFilterCheckBox->setText(tr("Show Tag Filter")); showTagsOnDeckPreviewsCheckBox->setText(tr("Show Tags On Deck Previews")); showBannerCardComboBoxCheckBox->setText(tr("Show Banner Card Selection Option")); - searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search")); drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); @@ -165,11 +155,6 @@ bool VisualDeckStorageQuickSettingsWidget::getShowTagsOnDeckPreviews() const return showTagsOnDeckPreviewsCheckBox->isChecked(); } -bool VisualDeckStorageQuickSettingsWidget::getSearchFolderNames() const -{ - return searchFolderNamesCheckBox->isChecked(); -} - int VisualDeckStorageQuickSettingsWidget::getUnusedColorIdentitiesOpacity() const { return unusedColorIdentitiesOpacitySpinBox->value(); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h index d27a9053b..11c1d64e9 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h @@ -23,7 +23,6 @@ class VisualDeckStorageQuickSettingsWidget : public SettingsButtonWidget QCheckBox *showBannerCardComboBoxCheckBox; QCheckBox *showTagFilterCheckBox; QCheckBox *showTagsOnDeckPreviewsCheckBox; - QCheckBox *searchFolderNamesCheckBox; QLabel *unusedColorIdentitiesOpacityLabel; QSpinBox *unusedColorIdentitiesOpacitySpinBox; QLabel *deckPreviewTooltipLabel; @@ -50,7 +49,6 @@ public: bool getShowBannerCardComboBox() const; bool getShowTagFilter() const; bool getShowTagsOnDeckPreviews() const; - bool getSearchFolderNames() const; int getUnusedColorIdentitiesOpacity() const; TooltipType getDeckPreviewTooltip() const; int getCardSize() const; @@ -61,7 +59,6 @@ signals: void showBannerCardComboBoxChanged(bool enabled); void showTagFilterChanged(bool enabled); void showTagsOnDeckPreviewsChanged(bool enabled); - void searchFolderNamesChanged(bool enabled); void unusedColorIdentitiesOpacityChanged(int opacity); void deckPreviewTooltipChanged(TooltipType tooltip); void cardSizeChanged(int scale); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp index 2998845f1..560e3c6c9 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -52,17 +52,15 @@ QString VisualDeckStorageSearchWidget::getSearchText() } /** - * Gets the filename used for the search. + * Converts the filepath into a relative filepath starting from the deck folder. + * If the file isn't in the deck folder, then this will just return the filename. * - * if includeFolderName is true, then this returns the relative filepath starting from the deck folder. - * If the file isn't in the deck folder, or includeFolderName is false, then this will just return the filename. - * - * @param filePath The filePath to convert into a search name + * @param filePath The filepath to convert into a relative filepath */ -static QString getFileSearchName(const QString &filePath, bool includeFolderName) +static QString toRelativeFilepath(const QString &filePath) { QString deckPath = SettingsCache::instance().getDeckPath(); - if (includeFolderName && filePath.startsWith(deckPath)) { + if (filePath.startsWith(deckPath)) { return filePath.mid(deckPath.length()); } @@ -71,14 +69,12 @@ static QString getFileSearchName(const QString &filePath, bool includeFolderName return fileName; } -void VisualDeckStorageSearchWidget::filterWidgets(QList widgets, - const QString &searchText, - bool includeFolderName) +void VisualDeckStorageSearchWidget::filterWidgets(QList widgets, const QString &searchText) { auto filterString = DeckFilterString(searchText); for (auto widget : widgets) { - QString fileSearchName = getFileSearchName(widget->filePath, includeFolderName); - widget->filteredBySearch = !filterString.check(widget, {fileSearchName}); + QString relativeFilePath = toRelativeFilepath(widget->filePath); + widget->filteredBySearch = !filterString.check(widget, {relativeFilePath}); } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h index 444ea9a27..e8b91bc5b 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -16,7 +16,7 @@ class VisualDeckStorageSearchWidget : public QWidget public: explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent); QString getSearchText(); - void filterWidgets(QList widgets, const QString &searchText, bool includeFolderName); + void filterWidgets(QList widgets, const QString &searchText); private: QHBoxLayout *layout; diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index e82f0c8d6..078b9337f 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -46,8 +46,6 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare &VisualDeckStorageWidget::updateShowFolders); connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::showTagFilterChanged, this, &VisualDeckStorageWidget::updateTagsVisibility); - connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::searchFolderNamesChanged, this, - &VisualDeckStorageWidget::updateSearchFilter); searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget); searchAndSortLayout->addWidget(sortWidget); @@ -196,8 +194,7 @@ void VisualDeckStorageWidget::updateColorFilter() void VisualDeckStorageWidget::updateSearchFilter() { if (folderWidget) { - searchWidget->filterWidgets(folderWidget->findChildren(), searchWidget->getSearchText(), - quickSettingsWidget->getSearchFolderNames()); + searchWidget->filterWidgets(folderWidget->findChildren(), searchWidget->getSearchText()); folderWidget->updateVisibility(); } } diff --git a/cockatrice/src/game/filters/deck_filter_string.h b/cockatrice/src/game/filters/deck_filter_string.h index dfdb1f1a8..df6eac783 100644 --- a/cockatrice/src/game/filters/deck_filter_string.h +++ b/cockatrice/src/game/filters/deck_filter_string.h @@ -17,9 +17,9 @@ inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string"); struct ExtraDeckSearchInfo { /** - * The filename used for filtering. Varies based on settings. + * The relative filepath starting from the deck folder */ - QString fileSearchName; + QString relativeFilePath; }; typedef std::function DeckFilter; diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index acd8cd581..68f7645ce 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -271,7 +271,6 @@ SettingsCache::SettingsCache() visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt(); visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool(); visualDeckStorageShowTagFilter = settings->value("interface/visualdeckstorageshowtagfilter", true).toBool(); - visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool(); visualDeckStorageShowBannerCardComboBox = settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool(); visualDeckStorageShowTagsOnDeckPreviews = @@ -731,12 +730,6 @@ void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTa emit visualDeckStorageShowTagFilterChanged(visualDeckStorageShowTagFilter); } -void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value) -{ - visualDeckStorageSearchFolderNames = value; - settings->setValue("interface/visualdeckstoragesearchfoldernames", visualDeckStorageSearchFolderNames); -} - void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox) { visualDeckStorageShowBannerCardComboBox = _showBannerCardComboBox; diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index f0861af58..ddf08c578 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -146,7 +146,6 @@ private: bool visualDeckStorageShowBannerCardComboBox; bool visualDeckStorageShowTagsOnDeckPreviews; bool visualDeckStorageShowTagFilter; - bool visualDeckStorageSearchFolderNames; int visualDeckStorageCardSize; bool visualDeckStorageDrawUnusedColorIdentities; int visualDeckStorageUnusedColorIdentitiesOpacity; @@ -453,10 +452,6 @@ public: { return visualDeckStorageShowTagFilter; } - bool getVisualDeckStorageSearchFolderNames() const - { - return visualDeckStorageSearchFolderNames; - } bool getVisualDeckStorageShowBannerCardComboBox() const { return visualDeckStorageShowBannerCardComboBox; @@ -851,7 +846,6 @@ public slots: void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder); void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value); void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags); - void setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value); void setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox); void setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags); void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index acd199759..ad9738a19 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -227,9 +227,6 @@ void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T /* value void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T /* _showTags */) { } -void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T /* value */) -{ -} void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T /* _showBannerCardComboBox */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 8ac925c0b..05d0d2244 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -231,9 +231,6 @@ void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T /* value void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T /* _showTags */) { } -void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T /* value */) -{ -} void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T /* _showBannerCardComboBox */) { } From 8d0b36d2d4cef1fccde0e6ea3f5db45a19756dac Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:16:35 -0700 Subject: [PATCH 31/32] [VDS] add deck search query options for name, filename, and path (#5975) * implement search expressions * update syntax help --- cockatrice/resources/help/deck_search.md | 14 ++++++++++ .../src/game/filters/deck_filter_string.cpp | 28 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index e2fa4fe37..95c237b16 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -8,6 +8,20 @@ searches are case insensitive.
[red deck wins](#red deck wins) (Any deck with a display name containing the words red, deck, and wins)
["red deck wins"](#%22red deck wins%22) (Any deck with a display name containing the exact phrase "red deck wins")
+
Deck Name:
+
[n:aggro](#n:aggro) (Any deck with a name containing the word aggro)
+
[n:red n:deck n:wins](#n:red n:deck n:wins) (Any deck with a name containing the words red, deck, and wins)
+
[n:"red deck wins"](#n:%22red deck wins%22) (Any deck with a name containing the exact phrase "red deck wins")
+ +
File Name:
+
[f:aggro](#f:aggro) (Any deck with a filename containing the word aggro)
+
[f:red f:deck f:wins](#f:red f:deck f:wins) (Any deck with a filename containing the words red, deck, and wins)
+
[f:"red deck wins"](#f:%22red deck wins%22) (Any deck with a filename containing the exact phrase "red deck wins")
+ +
Relative Path (starting from the deck folder):
+
[p:aggro](#p:aggro) (Any deck that has "aggro" somewhere in its relative path)
+
[p:edh/](#p:edh/) (Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)
+
Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
[[plains]] (Any deck that contains at least one card with "plains" in its name)
[[t:legendary]] (Any deck that contains at least one legendary)
diff --git a/cockatrice/src/game/filters/deck_filter_string.cpp b/cockatrice/src/game/filters/deck_filter_string.cpp index 13c39910d..4d493e5f1 100644 --- a/cockatrice/src/game/filters/deck_filter_string.cpp +++ b/cockatrice/src/game/filters/deck_filter_string.cpp @@ -12,7 +12,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart -QueryPart <- NotQuery / DeckContentQuery / GenericQuery +QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart @@ -20,6 +20,10 @@ DeckContentQuery <- CardSearch NumericExpression? CardSearch <- '[[' CardFilterString ']]' CardFilterString <- (!']]'.)* +DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String +FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String +PathQuery <- [Pp] 'ath'? [:] String + GenericQuery <- String NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. @@ -129,6 +133,28 @@ static void setupParserRules() return QString::fromStdString(std::string(sv.sv())); }; + search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto name = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { + return deck->deckLoader->getName().contains(name, Qt::CaseInsensitive); + }; + }; + + search["FileNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto name = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { + auto filename = QFileInfo(deck->filePath).fileName(); + return filename.contains(name, Qt::CaseInsensitive); + }; + }; + + search["PathQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto name = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) { + return info.relativeFilePath.contains(name, Qt::CaseInsensitive); + }; + }; + search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { From 0b34d20716e52a909ffec86ebd263e9efef1c4ca Mon Sep 17 00:00:00 2001 From: SlightlyCircuitous <71394296+SlightlyCircuitous@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:53:07 -0400 Subject: [PATCH 32/32] Add "landscapeOrientation" as a valid element (#5966) --- doc/carddatabase_v4/cards.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd index 732a5b5ba..9a879302b 100644 --- a/doc/carddatabase_v4/cards.xsd +++ b/doc/carddatabase_v4/cards.xsd @@ -56,6 +56,7 @@ +