diff --git a/.ci/Debian11/Dockerfile b/.ci/Debian11/Dockerfile deleted file mode 100644 index b994863bf..000000000 --- a/.ci/Debian11/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM debian:11 - -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 \ - ninja-build \ - protobuf-compiler \ - qt5-image-formats-plugins \ - qtmultimedia5-dev \ - qttools5-dev \ - qttools5-dev-tools \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* diff --git a/.ci/Fedora42/Dockerfile b/.ci/Fedora44/Dockerfile similarity index 95% rename from .ci/Fedora42/Dockerfile rename to .ci/Fedora44/Dockerfile index ee4a856f2..e6c8da7f3 100644 --- a/.ci/Fedora42/Dockerfile +++ b/.ci/Fedora44/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:42 +FROM fedora:44 RUN dnf install -y \ ccache \ diff --git a/.ci/Servatrice_Debian11/Dockerfile b/.ci/Servatrice_Debian12/Dockerfile similarity index 75% rename from .ci/Servatrice_Debian11/Dockerfile rename to .ci/Servatrice_Debian12/Dockerfile index fadc9e0e7..21f6a036e 100644 --- a/.ci/Servatrice_Debian11/Dockerfile +++ b/.ci/Servatrice_Debian12/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:11 +FROM debian:12 RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ @@ -11,11 +11,11 @@ RUN apt-get update && \ git \ libmariadb-dev-compat \ libprotobuf-dev \ - libqt5sql5-mysql \ - libqt5websockets5-dev \ + libqt6sql6-mysql \ ninja-build \ protobuf-compiler \ - qttools5-dev \ - qttools5-dev-tools \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-websockets-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/.ci/Ubuntu22.04/Dockerfile b/.ci/Ubuntu22.04/Dockerfile deleted file mode 100644 index ff2e6e43b..000000000 --- a/.ci/Ubuntu22.04/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM ubuntu:22.04 - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - build-essential \ - ccache \ - clang-format \ - cmake \ - file \ - g++ \ - git \ - libgl-dev \ - liblzma-dev \ - libmariadb-dev-compat \ - libprotobuf-dev \ - libqt6multimedia6 \ - libqt6sql6-mysql \ - libqt6svg6-dev \ - libqt6websockets6-dev \ - ninja-build \ - protobuf-compiler \ - qt6-image-formats-plugins \ - qt6-l10n-tools \ - qt6-multimedia-dev \ - qt6-tools-dev \ - qt6-tools-dev-tools \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* diff --git a/.ci/compile.sh b/.ci/compile.sh index 7ebdd6e4e..ee846897b 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -203,7 +203,11 @@ if [[ $RUNNER_OS == macOS ]]; then arch="x64" fi mkdir -p "$triplets_dir" - cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file" + triplet_source="../vcpkg/triplets/$arch-osx.cmake" + if [[ ! -f "$triplet_source" ]]; then + triplet_source="../vcpkg/triplets/community/$arch-osx.cmake" + fi + cp "$triplet_source" "$triplet_file" echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file" echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file" flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir") diff --git a/.ci/lint_cpp.sh b/.ci/lint_cpp.sh index cfb1e1f07..9786a83fc 100755 --- a/.ci/lint_cpp.sh +++ b/.ci/lint_cpp.sh @@ -13,17 +13,9 @@ fi # Check formatting using format.sh echo "Checking your code using format.sh..." -diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)" +./format.sh --color-diff --cmake --shell --print-version --branch origin/master err=$? -sep=" ----------- -" -used_version="${diff%%"$sep"*}" -diff="${diff#*"$sep"}" -changes_to_make="${diff%%"$sep"*}" -files_to_edit="${diff#*"$sep"}" - case $err in 1) cat < is the markdown comment escape sequence, emojis are way better generated_list="${generated_list//-->/→}" + # Escape & to preserve it from commit message into markdown output + generated_list="${generated_list//&/\\&}" body="${body//--REPLACE-WITH-GENERATED-LIST--/$generated_list}" body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}" body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}" diff --git a/.ci/release_template.md b/.ci/release_template.md index 1ce9f4bf7..ac78a193a 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -19,12 +19,10 @@ Available pre-compiled binaries for installation: LinuxUbuntu 26.04 LTS Resolute RacoonUbuntu 24.04 LTS Noble Numbat - • Ubuntu 22.04 LTS Jammy JellyfishDebian 13 TrixieDebian 12 Bookworm - • Debian 11 Bullseye + • Fedora 44Fedora 43 - • Fedora 42 We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00. General Linux support is available via a flatpak package at Flathub! @@ -85,7 +83,6 @@ Remove empty headers when done. ### Under the Hood ### Oracle ### Servatrice -### Webatrice diff --git a/.clang-format b/.clang-format index 1ef3ca6db..1db97481a 100644 --- a/.clang-format +++ b/.clang-format @@ -3,7 +3,9 @@ AccessModifierOffset: -4 ColumnLimit: 120 --- Language: Cpp -BreakBeforeBraces: Custom +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: None +BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: false @@ -18,16 +20,14 @@ BraceWrapping: SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true -AllowShortFunctionsOnASingleLine: None -BinPackParameters: false -AllowAllParametersOfDeclarationOnNextLine: false -IndentCaseLabels: true -PointerAlignment: Right -SortIncludes: true +BreakBeforeBraces: Custom IncludeBlocks: Regroup +IndentCaseLabels: true +InsertBraces: true +PointerAlignment: Right +RemoveSemicolon: true +SortIncludes: true StatementAttributeLikeMacros: [emit] -# requires clang-format 16 -# RemoveSemicolon: true --- Language: Proto AllowShortFunctionsOnASingleLine: None diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b293b3937..56ad64283 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -209,6 +209,16 @@ nowadays and clean it up for you. Lines should be 120 characters or less. Please break up lines that are too long into smaller parts, for example at spaces or after opening a brace. +### Documentation Comments ### + +Use [Doxygen](https://www.doxygen.nl/) for code documentation: + +- **Doc blocks**: Use `/** @brief Description */` (Javadoc-style), not `///` +- **Member comments**: Use trailing `///<` for inline member documentation +- **TODOs**: Use `//! \todo Description` (Qt-style), Doxygen collects them into a Todo List + (uses [Qt-style comments](https://www.doxygen.nl/manual/docblocks.html) with + Doxygen's [\todo command](https://www.doxygen.nl/manual/commands.html#cmdtodo)) + ### Memory Management ### New code should be written using references over pointers and stack allocation diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 88bed3663..404cac62e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: # Look for `.gitmodules` in the `root` directory directory: "/" ignore: - # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases) + # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) - dependency-name: "vcpkg" # Check for updates once a month schedule: @@ -39,13 +39,3 @@ updates: interval: "weekly" # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) open-pull-requests-limit: 2 - - # # Enable version updates for npm - # - package-ecosystem: "npm" - # # Look for `package.json` and `lock` files in the `webclient` subdirectory - # directory: "/webclient" - # # Check the npm registry for updates once a week - # schedule: - # interval: "weekly" - # # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) - # open-pull-requests-limit: 5 diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index aeed5da81..179fd824f 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -1,10 +1,10 @@ name: Build Desktop permissions: + actions: write # needed to delete entries in GHA cache (update ccache) + attestations: write # needed to persist the attestation. contents: write - id-token: write - attestations: write - actions: write # needed for ccache action to be able to delete gha caches + id-token: write # needed for signing certificate in attestation on: push: @@ -14,14 +14,12 @@ on: - '*/**' # matches all files not in root - '!**.md' - '!.github/**' - - '!.husky/**' - '!.tx/**' - '!doc/**' - - '!webclient/**' - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' - 'vcpkg.json' - - 'vcpkg' + - 'vcpkg' # needed to match submodule bumps (gitlink) tags: - '*' pull_request: @@ -29,14 +27,12 @@ on: - '*/**' # matches all files not in root - '!**.md' - '!.github/**' - - '!.husky/**' - '!.tx/**' - '!doc/**' - - '!webclient/**' - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' - 'vcpkg.json' - - 'vcpkg' + - 'vcpkg' # needed to match submodule bumps (gitlink) # Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) concurrency: @@ -46,13 +42,13 @@ concurrency: jobs: configure: name: Configure - runs-on: ubuntu-latest + runs-on: ubuntu-slim outputs: - tag: ${{steps.configure.outputs.tag}} - sha: ${{steps.configure.outputs.sha}} + tag: ${{ steps.configure.outputs.tag }} + sha: ${{ steps.configure.outputs.sha }} steps: - - name: Configure + - name: "Configure" id: configure shell: bash run: | @@ -68,154 +64,150 @@ jobs: fi echo "sha=$sha" >>"$GITHUB_OUTPUT" - - name: Checkout + - name: "Checkout" if: steps.configure.outputs.tag != null uses: actions/checkout@v6 with: - fetch-depth: 0 + fetch-depth: 0 # fetch all history for all branches and tags - - name: Prepare release parameters + - name: "Prepare release parameters" id: prepare if: steps.configure.outputs.tag != null shell: bash env: - TAG: ${{steps.configure.outputs.tag}} + TAG: ${{ steps.configure.outputs.tag }} run: .ci/prep_release.sh - - name: Create release + - name: "Create release" if: steps.configure.outputs.tag != null id: create_release shell: bash env: - GH_TOKEN: ${{github.token}} - tag_name: ${{steps.configure.outputs.tag}} - target: ${{steps.configure.outputs.sha}} - release_name: ${{steps.prepare.outputs.title}} - body_path: ${{steps.prepare.outputs.body_path}} - prerelease: ${{steps.prepare.outputs.is_beta}} + GH_TOKEN: ${{ github.token }} + tag_name: ${{ steps.configure.outputs.tag }} + target: ${{ steps.configure.outputs.sha }} + release_name: ${{ steps.prepare.outputs.title }} + body_path: ${{ steps.prepare.outputs.body_path }} + prerelease: ${{ steps.prepare.outputs.is_beta }} run: | - if [[ $prerelease == yes ]]; then - args="--prerelease" - fi - gh release create "$tag_name" --draft --verify-tag $args \ - --target "$target" --title "$release_name" \ - --notes-file "$body_path" + args=() + [[ $prerelease == yes ]] && args+=(--prerelease) + + gh release create "$tag_name" --verify-tag --draft "${args[@]}" \ + --target "$target" \ + --title "$release_name" \ + --notes-file "$body_path" build-linux: strategy: fail-fast: false matrix: - # These names correspond to the files in ".ci/$distro$version" + # The files in ".ci/$distro$version" correspond to the values given here include: - distro: Arch - package: skip # We are packaged in Arch already + allow-failure: yes - - - distro: Debian - version: 11 - package: DEB + package: skip # We are packaged in Arch already - distro: Servatrice_Debian - version: 11 + version: 12 + package: DEB - test: skip server_only: yes + test: skip - distro: Debian version: 12 + package: DEB test: skip # Running tests on all distros is superfluous - distro: Debian version: 13 + package: DEB - - distro: Fedora - version: 42 - package: RPM - test: skip # Running tests on all distros is superfluous - - distro: Fedora version: 43 + + package: RPM + test: skip # Running tests on all distros is superfluous + + - distro: Fedora + version: 44 + package: RPM - distro: Ubuntu - version: 22.04 + version: 24.04 + package: DEB test: skip # Running tests on all distros is superfluous - - distro: Ubuntu - version: 24.04 - package: DEB - - distro: Ubuntu version: 26.04 + package: DEB - name: ${{matrix.distro}} ${{matrix.version}} + name: ${{ matrix.distro }} ${{ matrix.version }} needs: configure runs-on: ubuntu-latest - continue-on-error: ${{matrix.allow-failure == 'yes'}} + continue-on-error: ${{ matrix.allow-failure == 'yes' }} timeout-minutes: 70 env: - NAME: ${{matrix.distro}}${{matrix.version}} - 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: 550M + CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache CCACHE_EVICTION_AGE: 7d + CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CMAKE_GENERATOR: 'Ninja' + NAME: ${{ matrix.distro }}${{ matrix.version }} steps: - - name: Checkout + - name: "Checkout" uses: actions/checkout@v6 - - name: Restore compiler cache (ccache) + - name: "Restore compiler cache (ccache)" id: ccache_restore uses: actions/cache/restore@v5 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}}- + key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }} + path: ${{ env.CACHE }} + restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}- - - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image + - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image" shell: bash run: source .ci/docker.sh --build - - name: Build debug and test + - name: "Build debug and test" if: matrix.test != 'skip' shell: bash run: | source .ci/docker.sh RUN --server --debug --test --ccache "$CCACHE_SIZE" \ - --cmake-generator "$CMAKE_GENERATOR" + --cmake-generator "$CMAKE_GENERATOR" - - name: Build release package + - name: "Build release package" id: build if: matrix.package != 'skip' shell: bash env: - SUFFIX: '-${{matrix.distro}}${{matrix.version}}' - package: '${{matrix.package}}' - server_only: '${{matrix.server_only}}' + SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}' + package: '${{ matrix.package }}' + server_only: '${{ matrix.server_only }}' run: | source .ci/docker.sh args=() - if [[ $server_only == yes ]]; then - args+=(--no-client) - fi - if [[ $GITHUB_REF == "refs/heads/master" ]]; then - args+=(--evict-ccache "$CCACHE_EVICTION_AGE") - fi + [[ $server_only == yes ]] && args+=(--no-client) + [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE") args+=(--ccache "$CCACHE_SIZE") args+=(--cmake-generator "$CMAKE_GENERATOR") args+=(--suffix "$SUFFIX") + RUN --server --release --package "$package" "${args[@]}" # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - - name: Delete remote compiler cache (ccache) + - name: "Delete remote compiler cache (ccache)" if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit continue-on-error: true env: @@ -225,47 +217,47 @@ jobs: echo "Cache deleted successfully" fi - - name: Save updated compiler cache (ccache) + - name: "Save updated compiler cache (ccache)" if: github.ref == 'refs/heads/master' uses: actions/cache/save@v5 with: - path: ${{env.CACHE}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} + path: ${{ env.CACHE }} - - name: Upload artifact + - name: "Upload artifact" id: upload_artifact if: matrix.package != 'skip' uses: actions/upload-artifact@v7 with: - path: ${{steps.build.outputs.path}} archive: false if-no-files-found: error + path: ${{ steps.build.outputs.path }} - - name: Upload to release + - name: "Upload to release" id: upload_release if: matrix.package != 'skip' && needs.configure.outputs.tag != null shell: bash env: - GH_TOKEN: ${{github.token}} - tag_name: ${{needs.configure.outputs.tag}} - asset_name: ${{steps.build.outputs.fullname}} - asset_path: ${{steps.build.outputs.path}} + asset_name: ${{ steps.build.outputs.fullname }} + asset_path: ${{ steps.build.outputs.path }} + GH_TOKEN: ${{ github.token }} + tag_name: ${{ needs.configure.outputs.tag }} run: gh release upload "$tag_name" "$asset_path#$asset_name" - - name: Attest binary provenance + - name: "Attest binary provenance" id: attestation if: steps.upload_release.outcome == 'success' uses: actions/attest@v4 with: - subject-path: ${{steps.build.outputs.path}} show-summary: false + subject-path: ${{ steps.build.outputs.path }} - - name: Verify binary attestation + - name: "Verify binary attestation" if: steps.attestation.outcome == 'success' shell: bash env: - GH_TOKEN: ${{github.token}} - run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice + GH_TOKEN: ${{ github.token }} + run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice build-vcpkg: strategy: @@ -275,200 +267,202 @@ jobs: - os: macOS target: 13 runner: macos-15-intel - soc: Intel - xcode: "16.4" - type: Release - override_target: 13 + + ccache_eviction_age: 7d + cmake_generator: Ninja make_package: 1 + override_target: 13 package_suffix: "-macOS13_Intel" - qt_version: 6.11.* + qt_version: 6.11.0 qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cmake_generator: Ninja + soc: Intel + type: Release use_ccache: 1 - ccache_eviction_age: 7d + xcode: "16.4" - os: macOS target: 14 runner: macos-14 - soc: Apple - xcode: "15.4" - type: Release + + ccache_eviction_age: 7d + cmake_generator: Ninja make_package: 1 package_suffix: "-macOS14" - qt_version: 6.11.* + qt_version: 6.11.0 qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cmake_generator: Ninja + soc: Apple + type: Release use_ccache: 1 - ccache_eviction_age: 7d + xcode: "15.4" - os: macOS target: 15 runner: macos-15 - soc: Apple - xcode: "16.4" - type: Release + + ccache_eviction_age: 7d + cmake_generator: Ninja make_package: 1 package_suffix: "-macOS15" - qt_version: 6.11.* + qt_version: 6.11.0 qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cmake_generator: Ninja + soc: Apple + type: Release use_ccache: 1 - ccache_eviction_age: 7d + xcode: "16.4" - os: macOS target: 15 runner: macos-15 - soc: Apple - xcode: "16.4" - type: Debug - qt_version: 6.11.* + + ccache_eviction_age: 7d + cmake_generator: Ninja + qt_version: 6.11.0 qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cmake_generator: Ninja + soc: Apple + type: Debug use_ccache: 1 - ccache_eviction_age: 7d + xcode: "16.4" - os: Windows target: 10 runner: windows-2025 - type: Release + + cmake_generator: "Visual Studio 18 2026" + cmake_generator_platform: x64 make_package: 1 package_suffix: "-Win10" - qt_version: 6.11.* + qt_version: 6.11.0 qt_arch: win64_msvc2022_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cmake_generator: "Visual Studio 17 2022" - cmake_generator_platform: x64 + type: Release - name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} + name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure - runs-on: ${{matrix.runner}} + runs-on: ${{ matrix.runner }} timeout-minutes: 100 env: - CCACHE_DIR: ${{github.workspace}}/.cache/ - # 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: 550M + CCACHE_DIR: ${{ github.workspace }}/.cache/ + CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy steps: - - name: Checkout + - name: "Checkout" uses: actions/checkout@v6 with: submodules: recursive - - name: Add msbuild to PATH + - name: "[Windows] Add msbuild to PATH" if: matrix.os == 'Windows' id: add-msbuild uses: microsoft/setup-msbuild@v3 with: msbuild-architecture: x64 - - name: Setup ccache - if: matrix.use_ccache == 1 && matrix.os == 'macOS' + - name: "[macOS] Setup ccache" + if: matrix.os == 'macOS' && matrix.use_ccache == 1 run: brew install ccache - - name: Restore compiler cache (ccache) - if: matrix.use_ccache == 1 + - name: "[macOS] Restore compiler cache (ccache)" + if: matrix.os == 'macOS' && matrix.use_ccache == 1 id: ccache_restore uses: actions/cache/restore@v5 env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - path: ${{env.CCACHE_DIR}} - key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} - restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- + key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }} + path: ${{ env.CCACHE_DIR }} + restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}- - - name: Install aqtinstall + - name: "Install aqtinstall" run: pipx install aqtinstall # Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases - - name: Resolve latest Qt patch version + - name: "Resolve latest Qt patch version" id: resolve_qt_version shell: bash - run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}" + run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}" - - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) + - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries" if: matrix.os == 'macOS' id: restore_qt uses: actions/cache/restore@v5 with: - path: ${{ github.workspace }}/Qt key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} + path: ${{ github.workspace }}/Qt # Using jurplel/install-qt-action to install Qt without using brew - # qt build using vcpkg either just fails or takes too long to build - - name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS) + # Qt build using vcpkg either just fails or takes too long to build + - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}" if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' uses: jurplel/install-qt-action@v4 with: - version: ${{ steps.resolve_qt_version.outputs.version }} - arch: ${{matrix.qt_arch}} - modules: ${{matrix.qt_modules}} + arch: ${{ matrix.qt_arch }} cache: false - dir: ${{github.workspace}} + dir: ${{ github.workspace }} + modules: ${{ matrix.qt_modules }} + version: ${{ steps.resolve_qt_version.outputs.version }} - - name: Thin Qt libraries (${{ matrix.soc }} macOS) + - name: "[macOS] Create thin Qt libraries" if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' run: .ci/thin_macos_qtlib.sh - - name: Cache thin Qt libraries (${{ matrix.soc }} macOS) + - name: "[macOS] Cache thin Qt libraries" if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' uses: actions/cache/save@v5 with: - path: ${{ github.workspace }}/Qt key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} + path: ${{ github.workspace }}/Qt - - name: Install Qt ${{matrix.qt_version}} (Windows) + - name: "[Windows] Install Qt ${{ matrix.qt_version }}" if: matrix.os == 'Windows' uses: jurplel/install-qt-action@v4 with: - # qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released + # Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released aqtsource: git+https://github.com/miurahr/aqtinstall.git - version: ${{ steps.resolve_qt_version.outputs.version }} - arch: ${{matrix.qt_arch}} - modules: ${{matrix.qt_modules}} + arch: ${{ matrix.qt_arch }} cache: true + modules: ${{ matrix.qt_modules }} + version: ${{ steps.resolve_qt_version.outputs.version }} - - name: Install NSIS + - name: "[Windows] Install NSIS" if: matrix.os == 'Windows' shell: bash run: choco install nsis - - name: Setup vcpkg cache + - name: "Setup vcpkg cache" id: vcpkg-cache uses: TAServers/vcpkg-cache@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - # uses environment variables, see compile.sh for more details - - name: Build Cockatrice + # Uses environment variables, see compile.sh for more details + - name: "Build Cockatrice" id: build shell: bash env: - BUILDTYPE: '${{matrix.type}}' - MAKE_PACKAGE: '${{matrix.make_package}}' - PACKAGE_SUFFIX: '${{matrix.package_suffix}}' - CMAKE_GENERATOR: ${{matrix.cmake_generator}} - CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}} - USE_CCACHE: ${{matrix.use_ccache}} - VCPKG_DISABLE_METRICS: 1 - VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite' - # macOS-specific environment variables, will be ignored on Windows - MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} - 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 }} - DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer' - TARGET_MACOS_VERSION: ${{ matrix.override_target }} + BUILDTYPE: '${{ matrix.type }}' CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }} + CMAKE_GENERATOR: ${{ matrix.cmake_generator }} + CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }} + DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer' + MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} + MAKE_PACKAGE: '${{ matrix.make_package }}' + PACKAGE_SUFFIX: '${{ matrix.package_suffix }}' + TARGET_MACOS_VERSION: ${{ matrix.override_target }} + USE_CCACHE: ${{ matrix.use_ccache }} + VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite' + VCPKG_DISABLE_METRICS: 1 run: .ci/compile.sh --server --test --vcpkg # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - - name: Delete remote compiler cache (ccache) - if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit + - name: "[macOS] Delete remote compiler cache (ccache)" + if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit continue-on-error: true env: GH_TOKEN: ${{ github.token }} @@ -477,14 +471,14 @@ jobs: echo "Cache deleted successfully" fi - - name: Save updated compiler cache (ccache) - if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 + - name: "[macOS] Save updated compiler cache (ccache)" + if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' uses: actions/cache/save@v5 with: - path: ${{env.CCACHE_DIR}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} + path: ${{ env.CCACHE_DIR }} - - name: Sign app bundle + - name: "[macOS] Sign app bundle" if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null id: sign_macos env: @@ -494,15 +488,15 @@ jobs: if [[ -n "$MACOS_CERTIFICATE_NAME" ]] then security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain - /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}} + /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "${{ steps.build.outputs.path }}" fi - - name: Notarize app bundle - if: steps.sign_macos.outcome == 'success' + - name: "[macOS] Notarize app bundle" + if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success' env: MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} - MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} + MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} run: | if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]] then @@ -514,7 +508,7 @@ jobs: # Therefore, we create a zip file containing our app bundle, so that we can send it to the # notarization service echo "Creating temp notarization archive" - ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip" + ditto -c -k --keepParent "${{ steps.build.outputs.path }}" "notarization.zip" # Here we send the notarization request to the Apple's Notarization service, waiting for the result. # This typically takes a few seconds inside a CI environment, but it might take more depending on the App @@ -526,51 +520,51 @@ jobs: # Finally, we need to "attach the staple" to our executable, which will allow our app to be # validated by macOS even when an internet connection is not available. echo "Attach staple" - xcrun stapler staple ${{steps.build.outputs.path}} + xcrun stapler staple "${{ steps.build.outputs.path }}" fi - - name: Upload artifact + - name: "Upload artifact" if: matrix.make_package id: upload_artifact uses: actions/upload-artifact@v7 with: - path: ${{steps.build.outputs.path}} archive: false if-no-files-found: error + path: ${{ steps.build.outputs.path }} - - name: Upload PDBs (Program Databases) + - name: "[Windows] Upload PDBs (Program Databases)" if: matrix.os == 'Windows' && github.ref_type != 'tag' uses: actions/upload-artifact@v7 with: - name: ${{steps.build.outputs.name}}-PDBs + if-no-files-found: error + name: ${{ steps.build.outputs.name }}-PDBs path: | build/cockatrice/Release/*.pdb build/oracle/Release/*.pdb build/servatrice/Release/*.pdb - if-no-files-found: error - - name: Upload to release + - name: "Upload to release" if: needs.configure.outputs.tag != null && matrix.make_package == '1' id: upload_release shell: bash env: - GH_TOKEN: ${{github.token}} - tag_name: ${{needs.configure.outputs.tag}} - asset_name: ${{steps.build.outputs.fullname}} - asset_path: ${{steps.build.outputs.path}} + asset_name: ${{ steps.build.outputs.fullname }} + asset_path: ${{ steps.build.outputs.path }} + GH_TOKEN: ${{ github.token }} + tag_name: ${{ needs.configure.outputs.tag }} run: gh release upload "$tag_name" "$asset_path#$asset_name" - - name: Attest binary provenance + - name: "Attest binary provenance" if: steps.upload_release.outcome == 'success' id: attestation uses: actions/attest@v4 with: - subject-path: ${{steps.build.outputs.path}} show-summary: false + subject-path: ${{ steps.build.outputs.path }} - - name: Verify binary attestation + - name: "Verify binary attestation" if: steps.attestation.outcome == 'success' shell: bash env: - GH_TOKEN: ${{github.token}} - run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice + GH_TOKEN: ${{ github.token }} + run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index 433f302a5..54931933c 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -1,17 +1,15 @@ name: Code Style (C++) on: - # push trigger not needed for linting, we do not allow direct pushes to master + # Push trigger not needed for linting, we do not allow direct pushes to master pull_request: paths: - '*/**' # matches all files not in root - '!**.md' - '!.ci/**' - '!.github/**' - - '!.husky/**' - '!.tx/**' - '!doc/**' - - '!webclient/**' - '.ci/lint_cpp.sh' - '.github/workflows/desktop-lint.yml' - '.clang-format' @@ -20,20 +18,23 @@ on: jobs: format: - runs-on: ubuntu-22.04 + runs-on: ubuntu-slim steps: - - name: Checkout + - name: "Checkout" uses: actions/checkout@v6 with: - fetch-depth: 20 # should be enough to find merge base + fetch-depth: 20 # should be enough to find merge base - - name: Install dependencies + - name: "Install dependencies" shell: bash run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck + sudo apt-get install -y --no-install-recommends \ + clang-format \ + cmake-format \ + shellcheck - - name: Check code formatting + - name: "Check code formatting" shell: bash run: ./.ci/lint_cpp.sh diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index fca1e97d4..d9ff06282 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -1,9 +1,11 @@ name: Build Docker Image +permissions: + contents: read + packages: write + on: push: - tags: - - '*Release*' branches: - master pull_request: @@ -12,61 +14,64 @@ on: paths: - '.github/workflows/docker-release.yml' - 'Dockerfile' + release: + types: + - released # publishing of stable releases # Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) concurrency: group: "${{ github.workflow }} @ ${{ github.ref_name }}" - cancel-in-progress: ${{ github.ref_type != 'tag' }} + cancel-in-progress: ${{ github.event_name != 'release' }} jobs: docker: name: amd64 & arm64 + if: ${{ github.repository_owner == 'Cockatrice' }} runs-on: ubuntu-latest - permissions: - contents: read - packages: write - + steps: - - name: Checkout + - name: "Checkout" uses: actions/checkout@v6 - - name: Docker metadata + - name: "Docker metadata" id: metadata uses: docker/metadata-action@v6 + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR with: + annotations: | + org.opencontainers.image.title=Servatrice + org.opencontainers.image.url=https://cockatrice.github.io/ + org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games images: | ghcr.io/cockatrice/servatrice labels: | org.opencontainers.image.title=Servatrice org.opencontainers.image.url=https://cockatrice.github.io/ org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games - annotations: | - org.opencontainers.image.title=Servatrice - org.opencontainers.image.url=https://cockatrice.github.io/ - org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games - - name: Set up QEMU + - name: "Set up QEMU" uses: docker/setup-qemu-action@v4 - - name: Set up Docker buildx + - name: "Set up Docker buildx" uses: docker/setup-buildx-action@v4 - - name: Login to GitHub Container Registry - if: github.ref_type == 'tag' + - name: "Login to GitHub Container Registry" + if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master' uses: docker/login-action@v4 with: + password: ${{ github.token }} registry: ghcr.io username: ${{ github.actor }} - password: ${{ github.token }} - - name: Build and push Docker image + - name: "Build and push Docker image" uses: docker/build-push-action@v7 with: - context: . - platforms: linux/amd64,linux/arm64 - push: ${{ github.ref_type == 'tag' }} - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} annotations: ${{ steps.metadata.outputs.annotations }} cache-from: type=gha,scope=servatrice cache-to: type=gha,mode=max,scope=servatrice + context: . + labels: ${{ steps.metadata.outputs.labels }} + platforms: linux/amd64,linux/arm64 + push: ${{ github.ref_type == 'tag' }} + tags: ${{ steps.metadata.outputs.tags }} diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index ce331d113..717999d5a 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -1,18 +1,18 @@ name: Generate Docs on: - push: - tags: - - '*' # Only re-generate docs when a new tagged version is pushed pull_request: paths: - 'doc/doxygen/**' - '.github/workflows/documentation-build.yml' - 'Doxyfile' + release: + types: + - published # publishing of stable releases and pre-releases workflow_dispatch: env: - COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name + COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name jobs: docs: @@ -20,22 +20,22 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code + - name: "Checkout code" uses: actions/checkout@v6 with: submodules: recursive - - name: Install Graphviz + - name: "Install Graphviz" run: | sudo apt-get install -y graphviz dot -V - - name: Install Doxygen + - name: "Install Doxygen" uses: ssciwr/doxygen-install@v2 with: - version: "1.14.0" + version: "1.16.1" - - name: Update Doxygen Configuration + - name: "Update Doxygen Configuration" run: | git diff Doxyfile doxygen -u Doxyfile @@ -48,16 +48,16 @@ jobs: exit 1 fi - - name: Generate Documentation + - name: "Generate Documentation" if: always() run: doxygen Doxyfile - - name: Deploy to cockatrice.github.io - if: github.event_name != 'pull_request' + - name: "Deploy to cockatrice.github.io" + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' uses: peaceiris/actions-gh-pages@v4 with: deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} + destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/ external_repository: Cockatrice/cockatrice.github.io publish_branch: master publish_dir: ./docs/html - destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/ diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index ed61e3b19..057381f8a 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -1,14 +1,14 @@ name: Update Translations on: - workflow_dispatch: - schedule: - # runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built - - cron: '0 0 15 1,4,7,10 *' pull_request: paths: - '.tx/**' - '.github/workflows/translations-pull.yml' + schedule: + # Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built + - cron: '0 0 15 1,4,7,10 *' + workflow_dispatch: jobs: translations: @@ -16,21 +16,21 @@ jobs: if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' name: Pull languages - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - name: Checkout repo + - name: "Checkout repo" uses: actions/checkout@v6 - - name: Pull translated strings from Transifex + - name: "Pull translated strings from Transifex" uses: transifex/cli-action@v2 with: - # used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config - # https://github.com/transifex/cli#pulling-files-from-transifex - token: ${{ secrets.TX_TOKEN }} + # Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config + # Docs: https://github.com/transifex/cli#pulling-files-from-transifex args: pull --force --all + token: ${{ secrets.TX_TOKEN }} - - name: Create pull request + - name: "Create pull request" if: github.event_name != 'pull_request' id: create_pr uses: peter-evans/create-pull-request@v8 @@ -38,13 +38,7 @@ jobs: add-paths: | cockatrice/translations/*.ts oracle/translations/*.ts - webclient/public/locales/*/translation.json - commit-message: Update translation files - # author is the owner of the commit - author: github-actions - branch: ci-update_translations - delete-branch: true - title: 'Update translations' + author: github-actions # owner of the commit body: | Pulled all translated strings from [Transifex][1]. @@ -54,12 +48,16 @@ jobs: [1]: https://explore.transifex.com/cockatrice/cockatrice/ [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster + branch: ci-update_translations + commit-message: Update translation files + delete-branch: true + draft: false labels: | CI Translation - draft: false + title: 'Update translations' - - name: PR Status + - name: "PR Status" if: github.event_name != 'pull_request' shell: bash env: diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index 777e9e6ac..4adcaf4a4 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -1,14 +1,14 @@ name: Update Translation Source on: - workflow_dispatch: - schedule: - # runs at the start of each quarter (UTC) - - cron: '0 0 1 1,4,7,10 *' pull_request: paths: - '.ci/update_translation_source_strings.sh' - '.github/workflows/translations-push.yml' + schedule: + # Runs at the start of each quarter (UTC) + - cron: '0 0 1 1,4,7,10 *' + workflow_dispatch: jobs: translations: @@ -16,19 +16,19 @@ jobs: if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' name: Push strings - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - name: Checkout repo + - name: "Checkout repo" uses: actions/checkout@v6 - - name: Install lupdate + - name: "Install lupdate" shell: bash run: | sudo apt-get update sudo apt-get install -y --no-install-recommends qttools5-dev-tools - - name: Update Cockatrice translation source + - name: "Update Cockatrice translation source" id: cockatrice shell: bash run: | @@ -36,17 +36,17 @@ jobs: export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')" FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh - - name: Update Oracle translation source + - name: "Update Oracle translation source" id: oracle shell: bash env: - FILE: 'oracle/oracle_en@source.ts' DIRS: 'oracle/src' + FILE: 'oracle/oracle_en@source.ts' run: .ci/update_translation_source_strings.sh - - name: Render template + - name: "Render template" id: template - uses: chuhlomin/render-template@v1 + uses: chuhlomin/render-template/binary@v1 with: template: .ci/update_translation_source_strings_template.md vars: | @@ -54,7 +54,7 @@ jobs: oracle_output: ${{ steps.oracle.outputs.output }} commit: ${{ github.sha }} - - name: Create pull request + - name: "Create pull request" if: github.event_name != 'pull_request' id: create_pr uses: peter-evans/create-pull-request@v8 @@ -62,19 +62,18 @@ jobs: add-paths: | cockatrice/cockatrice_en@source.ts oracle/oracle_en@source.ts - commit-message: Update translation source strings - # author is the owner of the commit - author: github-actions - branch: ci-update_translation_source - delete-branch: true - title: 'Update source strings' + author: github-actions # owner of the commit body: ${{ steps.template.outputs.result }} + branch: ci-update_translation_source + commit-message: Update translation source strings + delete-branch: true + draft: false labels: | CI Translation - draft: false + title: 'Update source strings' - - name: PR Status + - name: "PR Status" if: github.event_name != 'pull_request' shell: bash env: diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml deleted file mode 100644 index 8d756da02..000000000 --- a/.github/workflows/web-build.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build Web - -on: - push: - branches: - - master - paths: - - '.husky/**' - - 'webclient/**' - - '!**.md' - - '.github/workflows/web-build.yml' - pull_request: - paths: - - '.husky/**' - - 'webclient/**' - - '!**.md' - - '.github/workflows/web-build.yml' - -jobs: - build-web: - name: React (Node ${{matrix.node_version}}) - - runs-on: ubuntu-latest - - defaults: - run: - working-directory: webclient - - strategy: - fail-fast: false - matrix: - node_version: - - 16 - - lts/* - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: ${{matrix.node_version}} - cache: 'npm' - cache-dependency-path: 'webclient/package-lock.json' - - - name: Install dependencies - run: npm clean-install - - - name: Build app - run: npm run build - - - name: Test app - run: npm run test diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml deleted file mode 100644 index 8a90325e7..000000000 --- a/.github/workflows/web-lint.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Code Style (TypeScript) - -on: - # push trigger not needed for linting, we do not allow direct pushes to master - pull_request: - paths: - - 'webclient/**' - - '!**.md' - - '.github/workflows/web-lint.yml' - -jobs: - ESLint: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: webclient - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - cache: 'npm' - cache-dependency-path: 'webclient/package-lock.json' - - - name: Install ESLint - run: npm clean-install --ignore-scripts - - - name: Run ESLint - run: npm run lint diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 369435d6b..000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -cd webclient -npm run translate - -git add src/i18n-default.json diff --git a/.tx/config b/.tx/config index 9bc5ce950..8174a4dfd 100644 --- a/.tx/config +++ b/.tx/config @@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts file_filter = oracle/translations/oracle_.ts type = QT minimum_perc = 10 - -[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master] -resource_name = Webclient -source_lang = en -source_file = webclient/src/i18n-default.json -file_filter = webclient/public/locales//translation.json -type = KEYVALUEJSON -minimum_perc = 10 diff --git a/CMakeLists.txt b/CMakeLists.txt index fe808a652..c10e1db68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,11 +74,11 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -project("Cockatrice" VERSION 2.11.0) +project("Cockatrice" VERSION 3.1.0) # Set release name if not provided via env/cmake var if(NOT DEFINED GIT_TAG_RELEASENAME) - set(GIT_TAG_RELEASENAME "Omenpath") + set(GIT_TAG_RELEASENAME "Graduation Day") endif() # Use c++20 for all targets @@ -174,6 +174,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX) -Wno-error=delete-non-virtual-dtor -Wno-error=sign-compare -Wno-error=missing-declarations + -Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this ) foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS}) diff --git a/Dockerfile b/Dockerfile index d185b746a..7c5c773c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # -------- Build Stage -------- -FROM ubuntu:24.04 AS build +FROM ubuntu:26.04 AS build ARG DEBIAN_FRONTEND=noninteractive @@ -26,7 +26,7 @@ RUN mkdir build && cd build && \ # -------- Runtime Stage (clean) -------- -FROM ubuntu:24.04 +FROM ubuntu:26.04 RUN apt-get update && apt-get install -y --no-install-recommends \ libprotobuf32t64 \ diff --git a/Doxyfile b/Doxyfile index 0f2642fd8..fc96b5f22 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.14.0 +# Doxyfile 1.16.1 # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. @@ -361,6 +361,20 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as +# Markdown formatted also in cases where Doxygen's native markup format +# conflicts with that of Markdown. This is only relevant in cases where +# backticks are used. Doxygen's native markup style allows a single quote to end +# a text fragment started with a backtick and then treat it as a piece of quoted +# text, whereas in Markdown such text fragment is treated as verbatim and only +# ends when a second matching backtick is found. Also, Doxygen's native markup +# format requires double quotes to be escaped when they appear in a backtick +# section, whereas this is not needed for Markdown. +# The default value is: YES. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_STRICT = YES + # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. @@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES # This tag specifies a list of words that, when matching the start of a word in # the documentation, will suppress auto links generation, if it is enabled via -# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# -# or the \link or commands. +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using # +# or the \link or \ref commands. # This tag requires that the tag AUTOLINK_SUPPORT is set to YES. AUTOLINK_IGNORE_WORDS = @@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0 # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. +# Minimum value: 0, maximum value: 512, default value: 1. -NUM_PROC_THREADS = 1 +NUM_PROC_THREADS = 0 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to @@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES +# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the +# requirements page. When enabled, this page is automatically created when at +# least one comment block with a \requirement command appears in the input. +# The default value is: YES. + +GENERATE_REQUIREMENTS = YES + +# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on +# the requirements page (only relevant when using \requirement comment blocks). +# The setting NO will disable the traceablility information altogether. The +# setting UNSATISFIED_ONLY will show a list of requirements that are missing a +# satisfies relation (through the command: \satisfies). Similarly the setting +# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies +# relation (through the command: \verifies). Setting the tag to YES (the +# default) will show both lists if applicable. +# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY. +# The default value is: YES. +# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES. + +REQ_TRACEABILITY_INFO = YES + # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. @@ -1070,8 +1105,7 @@ EXCLUDE = build/ \ cmake/ \ doc/doxygen/theme/docs/ \ doc/doxygen/theme/include/ \ - vcpkg/ \ - webclient/ + vcpkg/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1887,7 +1921,7 @@ USE_MATHJAX = NO # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. -# Possible values are: MathJax_2 and MathJax_3. +# Possible values are: MathJax_2, MathJax_3 and MathJax_4. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1896,9 +1930,10 @@ MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4 # (see: -# http://docs.mathjax.org/en/latest/web/components/output.html). +# https://docs.mathjax.org/en/v4.0/output/index.htm). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported @@ -1911,36 +1946,50 @@ MATHJAX_VERSION = MathJax_2 MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. The default value is: +# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the +# destination directory should contain the MathJax.js script. For instance, if +# the mathjax directory is located at the same level as the HTML output +# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3 +# and 4 the destination directory should contain the tex-.js script +# (where is either chtml or svg). The default value points to the +# MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from https://www.mathjax.org before deployment. The default +# value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see -# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see -# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# https://docs.mathjax.org/en/v3.2/input/tex/extensions/): # MATHJAX_EXTENSIONS = ams +# For example for MathJax version 4 (see +# https://docs.mathjax.org/en/v4.0/input/tex/extensions/): +# MATHJAX_EXTENSIONS = units +# Note that for Mathjax version 4 quite a few extensions are already +# automatically loaded. To disable a package in Mathjax version 4 one can use +# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS += +# -textmacros) # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. +# of code that will be used on startup of the MathJax code. See the Mathjax site +# for more details: +# - MathJax version 2 (see: +# https://docs.mathjax.org/en/v2.7/) +# - MathJax version 3 (see: +# https://docs.mathjax.org/en/v3.2/) +# - MathJax version 4 (see: +# https://docs.mathjax.org/en/v4.0/) For an example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = @@ -2601,7 +2650,7 @@ HAVE_DOT = YES # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. -# Minimum value: 0, maximum value: 32, default value: 0. +# Minimum value: 0, maximum value: 512, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. DOT_NUM_THREADS = 0 diff --git a/README.md b/README.md index 8ad9a092c..9a27601cd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Related | Community | Contribute | - Build | + Build | Run

@@ -25,7 +25,6 @@ Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.

This project uses C++ and the Qt libraries.
-First work on a webclient with Typescript was started as well.
# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) @@ -48,6 +47,7 @@ Latest beta version: - [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice - [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage - [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice) +- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React) # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) @@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/ ### Translation [![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://explore.transifex.com/cockatrice/cockatrice/).
+Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice and Oracle 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/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!
-# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush) +# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* - [Qt](https://www.qt.io/developers/) diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 42060c835..485affe52 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -42,7 +42,7 @@ list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS) if(NOT FORCE_USE_QT5) # Linguist is now a component in Qt6 instead of an external package find_package( - Qt6 6.2.3 + Qt6 6.4.2 COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist QUIET HINTS ${Qt6_DIR} ) diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index 2fdc61fb9..7b52b7bcc 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -11,6 +11,7 @@ SetCompressor LZMA Var NormalDestDir Var PortableDestDir Var PortableMode +Var ReinstallMode !include LogicLib.nsh !include FileFunc.nsh @@ -28,13 +29,23 @@ Var PortableMode !define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now" !define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico" +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_WELCOME + +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE" + Page Custom PortableModePageCreate PortableModePageLeave !define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre !insertmacro MUI_PAGE_COMPONENTS + +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_DIRECTORY + +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_INSTFILES + +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM @@ -73,6 +84,7 @@ ${IfNot} ${Errors} MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\ /PORTABLE : Install in portable mode$\n\ /S : Silent install$\n\ + /R : Silent upgrade$\n\ /D=%directory% : Specify destination directory$\n" Quit ${EndIf} @@ -90,6 +102,16 @@ ${Else} ${EndIf} ${EndIf} +ClearErrors +${GetOptions} $9 "/R" $8 +${IfNot} ${Errors} + StrCpy $ReinstallMode 1 + SetSilent silent + SetAutoClose true +${Else} + StrCpy $ReinstallMode 0 +${EndIf} + ${If} $InstDir == "" ; User did not use /D to specify a directory, ; we need to set a default based on the install mode @@ -97,6 +119,22 @@ ${If} $InstDir == "" ${EndIf} Call SetModeDestinationFromInstdir +; --- Detect portable install when using /R --- +${If} $ReinstallMode = 1 + IfFileExists "$InstDir\portable.dat" 0 not_portable + StrCpy $PortableMode 1 + Goto portable_done + + not_portable: + StrCpy $PortableMode 0 + + portable_done: +${EndIf} + +${If} $ReinstallMode = 1 + Call AutoUninstallIfNeeded +${EndIf} + FunctionEnd Function un.onInit @@ -126,8 +164,46 @@ ${Else} ${EndIf} FunctionEnd +Function SkipIfReinstall +${If} $ReinstallMode = 1 + Abort +${EndIf} +FunctionEnd + +Function AutoUninstallIfNeeded + +SetShellVarContext all + +; --- 32-bit uninstall --- +SetRegView 32 +ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" + +StrCmp $R0 "" done32 +DetailPrint "Removing previous version (32-bit)..." +ExecWait '$R0' + +done32: + +; --- 64-bit uninstall --- +${If} ${RunningX64} + SetRegView 64 + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" + + StrCmp $R0 "" done64 + DetailPrint "Removing previous version (64-bit)..." + ExecWait '$R0' + + done64: +${EndIf} + +FunctionEnd Function PortableModePageCreate + +${If} $ReinstallMode = 1 + Abort +${EndIf} + Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory !insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice." nsDialogs::Create 1018 @@ -159,6 +235,11 @@ ${EndIf} FunctionEnd Function componentsPagePre + +${If} $ReinstallMode = 1 + Return +${EndIf} + ${If} $PortableMode = 0 SetShellVarContext all @@ -168,8 +249,12 @@ ${If} $PortableMode = 0 ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" StrCmp $R0 "" done32 - MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32 - Abort + ${If} $ReinstallMode = 0 + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32 + Abort + ${Else} + Goto uninst32 + ${EndIf} uninst32: ClearErrors @@ -184,8 +269,12 @@ ${If} $PortableMode = 0 ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" StrCmp $R0 "" done64 - MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64 - Abort + ${If} $ReinstallMode = 0 + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64 + Abort + ${Else} + Goto uninst64 + ${EndIf} uninst64: ClearErrors @@ -277,6 +366,12 @@ ${Else} FileWrite $0 "PORTABLE" FileClose $0 ${EndIf} + +${If} $ReinstallMode = 1 + IfFileExists "$INSTDIR\cockatrice.exe" 0 +2 + Exec '"$INSTDIR\cockatrice.exe"' +${EndIf} + SectionEnd Section "Start menu item" SecStartMenu diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake index cb27ebd87..b24b520d5 100644 --- a/cmake/getversion.cmake +++ b/cmake/getversion.cmake @@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})") # Format: [-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label] set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}") if(PROJECT_VERSION_RELEASENAME) - set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}") + string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}") + set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}") endif() set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}") diff --git a/cmake/gtest-CMakeLists.txt.in b/cmake/gtest-CMakeLists.txt.in index 2d71a55e5..2062d7f8c 100644 --- a/cmake/gtest-CMakeLists.txt.in +++ b/cmake/gtest-CMakeLists.txt.in @@ -1,15 +1,16 @@ -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.10) project(gtest-download LANGUAGES NONE) include(ExternalProject) -ExternalProject_Add(googletest - URL https://github.com/google/googletest/archive/release-1.11.0.zip - URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0 +externalproject_add( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip + URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28 SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src" BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build" CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" ) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 12733afe6..bd99d08bf 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${ set(cockatrice_SOURCES ${VERSION_STRING_CPP} # sort by alphabetical order, so that there is no debate about where to add new sources to the list + src/client/network/connection_controller/remote_connection_controller.cpp src/client/network/update/client/update_downloader.cpp src/client/network/interfaces/deck_stats_interface.cpp src/client/network/interfaces/tapped_out_interface.cpp @@ -55,68 +56,73 @@ set(cockatrice_SOURCES src/filters/filter_tree_model.cpp src/filters/syntax_help.cpp src/game/abstract_game.cpp - src/game/board/abstract_card_drag_item.cpp - src/game/board/abstract_card_item.cpp - src/game/board/abstract_counter.cpp - src/game/board/arrow_item.cpp - src/game/board/arrow_target.cpp - src/game/board/card_drag_item.cpp - src/game/board/card_item.cpp + src/game/arrow_registry.cpp + src/game_graphics/board/abstract_card_drag_item.cpp + src/game_graphics/board/abstract_card_item.cpp + src/game_graphics/board/abstract_counter.cpp + src/game/board/arrow_data.cpp + src/game_graphics/board/arrow_item.cpp + src/game_graphics/board/arrow_target.cpp + src/game_graphics/board/card_drag_item.cpp + src/game_graphics/board/card_item.cpp src/game/board/card_list.cpp - src/game/board/counter_general.cpp - src/game/board/translate_counter_name.cpp - src/game/deckview/deck_view.cpp - src/game/deckview/deck_view_container.cpp - src/game/deckview/tabbed_deck_view_container.cpp - src/game/dialogs/dlg_create_token.cpp - src/game/dialogs/dlg_move_top_cards_until.cpp - src/game/dialogs/dlg_roll_dice.cpp + src/game/board/card_state.cpp + src/game_graphics/board/counter_general.cpp + src/game/board/counter_state.cpp + src/game_graphics/board/translate_counter_name.cpp + src/game_graphics/deckview/deck_view.cpp + src/game_graphics/deckview/deck_view_container.cpp + src/game_graphics/deckview/tabbed_deck_view_container.cpp + src/game_graphics/dialogs/dlg_create_token.cpp + src/game_graphics/dialogs/dlg_move_top_cards_until.cpp + src/game_graphics/dialogs/dlg_roll_dice.cpp src/game/game.cpp src/game/game_event_handler.cpp src/game/game_meta_info.cpp - src/game/game_scene.cpp + src/game_graphics/game_scene.cpp src/game/game_state.cpp - src/game/game_view.cpp - src/game/hand_counter.cpp - src/game/log/message_log_widget.cpp + src/game_graphics/game_view.cpp + src/game_graphics/hand_counter.cpp + src/game_graphics/log/message_log_widget.cpp src/game/phase.cpp - src/game/phases_toolbar.cpp - src/game/player/menu/card_menu.cpp - src/game/player/menu/custom_zone_menu.cpp - src/game/player/menu/grave_menu.cpp - src/game/player/menu/hand_menu.cpp - src/game/player/menu/library_menu.cpp - src/game/player/menu/move_menu.cpp - src/game/player/menu/player_menu.cpp - src/game/player/menu/pt_menu.cpp - src/game/player/menu/rfg_menu.cpp - src/game/player/menu/say_menu.cpp - src/game/player/menu/sideboard_menu.cpp - src/game/player/menu/utility_menu.cpp - src/game/player/player.cpp + src/game_graphics/phases_toolbar.cpp + src/game_graphics/player/menu/card_menu.cpp + src/game_graphics/player/menu/custom_zone_menu.cpp + src/game_graphics/player/menu/grave_menu.cpp + src/game_graphics/player/menu/hand_menu.cpp + src/game_graphics/player/menu/library_menu.cpp + src/game_graphics/player/menu/move_menu.cpp + src/game_graphics/player/menu/player_menu.cpp + src/game_graphics/player/menu/pt_menu.cpp + src/game_graphics/player/menu/rfg_menu.cpp + src/game_graphics/player/menu/say_menu.cpp + src/game_graphics/player/menu/sideboard_menu.cpp + src/game_graphics/player/menu/utility_menu.cpp src/game/player/player_actions.cpp - src/game/player/player_area.cpp + src/game_graphics/player/player_area.cpp + src/game_graphics/player/player_dialogs.cpp src/game/player/player_event_handler.cpp - src/game/player/player_graphics_item.cpp + src/game_graphics/player/player_graphics_item.cpp src/game/player/player_info.cpp - src/game/player/player_list_widget.cpp + src/game_graphics/player/player_list_widget.cpp + src/game/player/player_logic.cpp src/game/player/player_manager.cpp - src/game/player/player_target.cpp + src/game_graphics/player/player_target.cpp src/game/replay.cpp - src/game/zones/card_zone.cpp - src/game/zones/hand_zone.cpp - src/game/zones/logic/card_zone_logic.cpp - src/game/zones/logic/hand_zone_logic.cpp - src/game/zones/logic/pile_zone_logic.cpp - src/game/zones/logic/stack_zone_logic.cpp - src/game/zones/logic/table_zone_logic.cpp - src/game/zones/logic/view_zone_logic.cpp - src/game/zones/pile_zone.cpp - src/game/zones/select_zone.cpp - src/game/zones/stack_zone.cpp - src/game/zones/table_zone.cpp - src/game/zones/view_zone.cpp - src/game/zones/view_zone_widget.cpp + src/game/zones/card_zone_logic.cpp + src/game/zones/hand_zone_logic.cpp + src/game/zones/pile_zone_logic.cpp + src/game/zones/stack_zone_logic.cpp + src/game/zones/table_zone_logic.cpp + src/game/zones/view_zone_logic.cpp + src/game_graphics/zones/card_zone.cpp + src/game_graphics/zones/hand_zone.cpp + src/game_graphics/zones/pile_zone.cpp + src/game_graphics/zones/select_zone.cpp + src/game_graphics/zones/stack_zone.cpp + src/game_graphics/zones/table_zone.cpp + src/game_graphics/zones/view_zone.cpp + src/game_graphics/zones/view_zone_widget.cpp src/game_graphics/board/abstract_graphics_item.cpp src/interface/card_picture_loader/card_picture_loader.cpp src/interface/card_picture_loader/card_picture_loader_local.cpp @@ -129,7 +135,13 @@ set(cockatrice_SOURCES src/interface/layouts/overlap_layout.cpp src/interface/widgets/utility/line_edit_completer.cpp src/interface/pixel_map_generator.cpp + src/interface/theme_config.cpp src/interface/theme_manager.cpp + src/interface/palette_editor/color_button.cpp + src/interface/palette_editor/palette_generator.cpp + src/interface/palette_editor/quick_setup_panel.cpp + src/interface/palette_editor/palette_grid_widget.cpp + src/interface/palette_editor/palette_editor_dialog.cpp src/interface/widgets/cards/additional_info/color_identity_widget.cpp src/interface/widgets/cards/additional_info/mana_cost_widget.cpp src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp @@ -171,6 +183,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp + src/interface/widgets/deck_editor/card_database_view.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp @@ -216,6 +229,7 @@ set(cockatrice_SOURCES src/interface/widgets/replay/replay_manager.cpp src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/server/chat_view/chat_view.cpp + src/interface/widgets/server/game_filter_configs.cpp src/interface/widgets/server/game_selector.cpp src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp src/interface/widgets/server/games_model.cpp @@ -227,6 +241,14 @@ set(cockatrice_SOURCES src/interface/widgets/server/user/user_info_connection.cpp src/interface/widgets/server/user/user_list_manager.cpp src/interface/widgets/server/user/user_list_widget.cpp + src/interface/widgets/settings_page/appearance_settings_page.cpp + src/interface/widgets/settings_page/deck_editor_settings_page.cpp + src/interface/widgets/settings_page/general_settings_page.cpp + src/interface/widgets/settings_page/messages_settings_page.cpp + src/interface/widgets/settings_page/shortcut_settings_page.cpp + src/interface/widgets/settings_page/sound_settings_page.cpp + src/interface/widgets/settings_page/storage_settings_page.cpp + src/interface/widgets/settings_page/user_interface_settings_page.cpp src/interface/widgets/utility/custom_line_edit.cpp src/interface/widgets/utility/get_text_with_max.cpp src/interface/widgets/utility/sequence_edit.cpp @@ -325,6 +347,8 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h + src/interface/widgets/utility/compact_push_button.cpp + src/interface/widgets/utility/compact_push_button.h ) add_subdirectory(sounds) @@ -539,6 +563,7 @@ if(WIN32) DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/" DESTINATION ./ FILES_MATCHING + PATTERN "CMakeFiles" EXCLUDE PATTERN "*.ini" ) diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 37fb145f0..9c34929b7 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -55,6 +55,7 @@ resources/icons/view.svg resources/icons/mana/B.svg + resources/icons/mana/C.svg resources/icons/mana/G.svg resources/icons/mana/R.svg resources/icons/mana/U.svg @@ -69,6 +70,7 @@ resources/config/interface.svg resources/config/messages.svg resources/config/deckeditor.svg + resources/config/storage.svg resources/config/shorcuts.svg resources/config/sound.svg resources/config/debug.ini diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 20aa206ce..7ac0d9ca4 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -28,6 +28,8 @@ #dlg_tip_of_the_day = true #dlg_update = true +#general_settings_page = true + #settings_cache = true #servers_settings = true #shortcuts_settings = true diff --git a/cockatrice/resources/config/storage.svg b/cockatrice/resources/config/storage.svg new file mode 100644 index 000000000..de85228dc --- /dev/null +++ b/cockatrice/resources/config/storage.svg @@ -0,0 +1,799 @@ + + + +image/svg+xml diff --git a/cockatrice/resources/icons/mana/C.svg b/cockatrice/resources/icons/mana/C.svg new file mode 100644 index 000000000..eb09fb872 --- /dev/null +++ b/cockatrice/resources/icons/mana/C.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp new file mode 100644 index 000000000..75cedcafc --- /dev/null +++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp @@ -0,0 +1,587 @@ +#include "remote_connection_controller.h" + +#include "../../settings/cache_settings.h" +#include "../interface/widgets/dialogs/dlg_connect.h" +#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" +#include "../interface/widgets/dialogs/dlg_forgot_password_request.h" +#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" +#include "../interface/widgets/dialogs/dlg_register.h" +#include "../interface/widgets/utility/get_text_with_max.h" + +#include +#include +#include +#include +#include +#include + +ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent) + : QObject(parent), dialogParent(dialogParent) +{ + remoteClient = new RemoteClient(nullptr, &SettingsCache::instance()); + + clientThread = new QThread(this); + remoteClient->moveToThread(clientThread); + clientThread->start(); + + wireClientSignals(); +} + +ConnectionController::~ConnectionController() +{ + remoteClient->deleteLater(); + clientThread->wait(); +} + +void ConnectionController::wireClientSignals() +{ + connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this, + &ConnectionController::onConnectionClosedEvent); + + connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this, + &ConnectionController::onServerShutdownEvent); + + connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged); + + connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived, + Qt::BlockingQueuedConnection); + + connect(remoteClient, &RemoteClient::loginError, this, + [this](Response::ResponseCode r, QString rs, quint32 et, QList mf) { + onLoginError(static_cast(r), rs, et, mf); + }); + + connect(remoteClient, &RemoteClient::registerError, this, + [this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast(r), rs, et); }); + + connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError); + connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError); + connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout); + + connect(remoteClient, &RemoteClient::protocolVersionMismatch, this, + &ConnectionController::onProtocolVersionMismatch); + + connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted); + + connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this, + &ConnectionController::onRegisterAcceptedNeedsActivate); + + connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted); + + connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate); + + connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this, + &ConnectionController::onForgotPasswordSuccess); + + connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError); + + connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this, + &ConnectionController::onPromptForgotPasswordReset); + + connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this, + &ConnectionController::onPromptForgotPasswordChallenge); +} + +void ConnectionController::connectToServer() +{ + dlgConnect = new DlgConnect(dialogParent); + connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest); + + if (dlgConnect->exec()) { + remoteClient->connectToServer(dlgConnect->getHost(), static_cast(dlgConnect->getPort()), + dlgConnect->getPlayerName(), dlgConnect->getPassword()); + } +} + +void ConnectionController::connectToServerDirect(const QString &host, + unsigned int port, + const QString &playerName, + const QString &password) +{ + remoteClient->connectToServer(host, port, playerName, password); +} + +void ConnectionController::disconnectFromServer() +{ + remoteClient->disconnectFromServer(); +} + +void ConnectionController::registerToServer() +{ + DlgRegister dlg(dialogParent); + if (dlg.exec()) { + remoteClient->registerToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(), + dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName()); + } +} + +void ConnectionController::forgotPasswordRequest() +{ + DlgForgotPasswordRequest dlg(dialogParent); + if (dlg.exec()) { + remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast(dlg.getPort()), + dlg.getPlayerName()); + } +} + +void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event) +{ + remoteClient->disconnectFromServer(); + + QString reasonStr; + switch (event.reason()) { + case Event_ConnectionClosed::USER_LIMIT_REACHED: { + reasonStr = tr("The server has reached its maximum user capacity, please check back later."); + break; + } + case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: { + reasonStr = tr("There are too many concurrent connections from your address."); + break; + } + case Event_ConnectionClosed::BANNED: { + reasonStr = tr("Banned by moderator"); + if (event.has_end_time()) { + reasonStr.append( + "\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString())); + } else { + reasonStr.append("\n" + tr("This ban lasts indefinitely.")); + } + if (event.has_reason_str()) { + reasonStr.append("\n\n" + QString::fromStdString(event.reason_str())); + } + break; + } + case Event_ConnectionClosed::SERVER_SHUTDOWN: { + reasonStr = tr("Scheduled server shutdown."); + break; + } + case Event_ConnectionClosed::USERNAMEINVALID: { + reasonStr = tr("Invalid username."); + break; + } + case Event_ConnectionClosed::LOGGEDINELSEWERE: { + reasonStr = tr("You have been logged out due to logging in at another location."); + break; + } + default: + reasonStr = QString::fromStdString(event.reason_str()); + } + + QMessageBox::critical(dialogParent, tr("Connection closed"), + tr("The server has terminated your connection.\nReason: %1").arg(reasonStr)); +} + +void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event) +{ + serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running " + "games will be lost.\nReason for shutdown: %1", + "", event.minutes()) + .arg(QString::fromStdString(event.reason()))); + serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64)); + serverShutdownMessageBox.setText(tr("Scheduled server shutdown")); + serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal); + serverShutdownMessageBox.setVisible(true); +} + +void ConnectionController::onStatusChanged(ClientStatus status) +{ + // Update the window title first, then let MainWindow handle its own UI + // state via the forwarded signal + updateWindowTitle(); + emit statusChanged(status); + + // TabSupervisor::stop() needs calling on disconnect; start() is driven by + // onUserInfoReceived → tabSupervisorStartRequested. + if (status == StatusDisconnected) { + emit tabSupervisorStopRequested(); + } +} + +void ConnectionController::onUserInfoReceived(const ServerInfo_User &info) +{ + emit tabSupervisorStartRequested(info); +} + +void ConnectionController::onLoginError(int r, + QString reasonStr, + quint32 endTime, + const QList &missingFeatures) +{ + switch (static_cast(r)) { + case Response::RespClientUpdateRequired: { + QString formatted = "Missing Features: "; + for (int i = 0; i < missingFeatures.size(); ++i) { + formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i)); + } + + QMessageBox msgBox(dialogParent); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setWindowTitle(tr("Failed Login")); + msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") + + "\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'.")); + msgBox.setDetailedText(formatted); + msgBox.exec(); + break; + } + + case Response::RespWrongPassword: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("Incorrect username or password. " + "Please check your authentication information and try again.")); + break; + } + + case Response::RespWouldOverwriteOldSession: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("There is already an active session using this user name.\n" + "Please close that session first and re-login.")); + break; + } + + case Response::RespUserIsBanned: { + QString bannedStr = + endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString()) + : tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) { + bannedStr.append("\n\n" + reasonStr); + } + QMessageBox::critical(dialogParent, tr("Error"), bannedStr); + break; + } + + case Response::RespUsernameInvalid: { + QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr)); + break; + } + + case Response::RespRegistrationRequired: { + if (QMessageBox::question(dialogParent, tr("Error"), + tr("This server requires user registration. Do you want to register now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + registerToServer(); + } + return; // don't re-prompt connect + } + + case Response::RespClientIdRequired: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("This server requires client IDs. Your client is either failing to generate an " + "ID or you are running a modified client.\n" + "Please close and reopen your client to try again.")); + break; + } + + case Response::RespContextError: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("An internal error has occurred, please close and reopen Cockatrice before " + "trying again.\nIf the error persists, ensure you are running the latest " + "version of the software and if needed contact the software developers.")); + break; + } + + case Response::RespAccountNotActivated: { + bool ok = false; + QString token = + getTextWithMax(dialogParent, tr("Account activation"), + tr("Your account has not been activated yet.\n" + "You need to provide the activation token received in the activation email."), + QLineEdit::Normal, QString(), &ok); + + if (ok && !token.isEmpty()) { + remoteClient->activateToServer(token); + return; + } + remoteClient->disconnectFromServer(); + return; + } + + case Response::RespServerFull: { + QMessageBox::critical(dialogParent, tr("Server Full"), + tr("The server has reached its maximum user capacity, please check back later.")); + break; + } + + default: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("Unknown login error: %1").arg(r) + + tr("\nThis usually means that your client version is out of date, and the server " + "sent a reply your client doesn't understand.")); + break; + } + } + + // Re-open the connect dialog after any handled error + connectToServer(); +} + +void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime) +{ + switch (static_cast(r)) { + case Response::RespRegistrationDisabled: { + QMessageBox::critical(dialogParent, tr("Registration denied"), + tr("Registration is currently disabled on this server")); + break; + } + case Response::RespUserAlreadyExists: { + QMessageBox::critical(dialogParent, tr("Registration denied"), + tr("There is already an existing account with the same user name.")); + break; + } + case Response::RespEmailRequiredToRegister: { + QMessageBox::critical(dialogParent, tr("Registration denied"), + tr("It's mandatory to specify a valid email address when registering.")); + break; + } + case Response::RespEmailBlackListed: { + if (reasonStr.isEmpty()) { + reasonStr = + "The email address provider used during registration has been blocked from use on this server."; + } + QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr); + break; + } + case Response::RespTooManyRequests: { + QMessageBox::critical(dialogParent, tr("Registration denied"), + tr("It appears you are attempting to register a new account on this server yet you " + "already have an account registered with the email provided. This server " + "restricts the number of accounts a user can register per address. Please " + "contact the server operator for further assistance or to obtain your " + "credential information.")); + break; + } + case Response::RespPasswordTooShort: { + QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short.")); + break; + } + case Response::RespUserIsBanned: { + QString bannedStr = + endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString()) + : tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) { + bannedStr.append("\n\n" + reasonStr); + } + QMessageBox::critical(dialogParent, tr("Error"), bannedStr); + break; + } + case Response::RespUsernameInvalid: { + QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr)); + break; + } + case Response::RespRegistrationFailed: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("Registration failed for a technical problem on the server.")); + break; + } + case Response::RespNotConnected: { + QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost.")); + break; + } + default: { + QMessageBox::critical(dialogParent, tr("Error"), + tr("Unknown registration error: %1").arg(r) + + tr("\nThis usually means that your client version is out of date, and the server " + "sent a reply your client doesn't understand.")); + break; + } + } + + registerToServer(); +} + +void ConnectionController::onActivateError() +{ + QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed")); + remoteClient->disconnectFromServer(); + connectToServer(); +} + +void ConnectionController::onSocketError(const QString &errorStr) +{ + QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr)); + connectToServer(); +} + +void ConnectionController::onServerTimeout() +{ + QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout")); + connectToServer(); +} + +void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion) +{ + if (localVersion > remoteVersion) { + QMessageBox::critical(dialogParent, tr("Error"), + tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice " + "version or connect to a suitable server.\n" + "Local version is %1, remote version is %2.") + .arg(localVersion) + .arg(remoteVersion)); + } else { + QMessageBox::critical(dialogParent, tr("Error"), + tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n" + "Local version is %1, remote version is %2.") + .arg(localVersion) + .arg(remoteVersion)); + } +} + +void ConnectionController::onRegisterAccepted() +{ + QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login.")); +} + +void ConnectionController::onRegisterAcceptedNeedsActivate() +{ + // Server will send activation email; nothing to display here. +} + +void ConnectionController::onActivateAccepted() +{ + QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login.")); +} + +void ConnectionController::onNotifyUserAboutUpdate() +{ + QMessageBox::information( + dialogParent, tr("Information"), + tr("This server supports additional features that your client doesn't have.\n" + "This is most likely not a problem, but this message might mean there is a new version of " + "Cockatrice available or this server is running a custom or pre-release version.\n\n" + "To update your client, go to Help -> Check for Updates.")); +} + +void ConnectionController::onForgotPasswordSuccess() +{ + QMessageBox::information( + dialogParent, tr("Reset Password"), + tr("Your password has been reset successfully, you can now log in using the new credentials.")); + SettingsCache::instance().servers().setFPHostName(""); + SettingsCache::instance().servers().setFPPort(""); + SettingsCache::instance().servers().setFPPlayerName(""); +} + +void ConnectionController::onForgotPasswordError() +{ + QMessageBox::warning( + dialogParent, tr("Reset Password"), + tr("Failed to reset user account password, please contact the server operator to reset your password.")); + SettingsCache::instance().servers().setFPHostName(""); + SettingsCache::instance().servers().setFPPort(""); + SettingsCache::instance().servers().setFPPlayerName(""); +} + +void ConnectionController::onPromptForgotPasswordReset() +{ + QMessageBox::information(dialogParent, tr("Reset Password"), + tr("Activation request received, please check your email for an activation token.")); + DlgForgotPasswordReset dlg(dialogParent); + if (dlg.exec()) { + remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast(dlg.getPort()), + dlg.getPlayerName(), dlg.getToken(), dlg.getPassword()); + } +} + +void ConnectionController::onPromptForgotPasswordChallenge() +{ + DlgForgotPasswordChallenge dlg(dialogParent); + if (dlg.exec()) { + remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast(dlg.getPort()), + dlg.getPlayerName(), dlg.getEmail()); + } +} + +void ConnectionController::updateWindowTitle() +{ + const QString appName = QStringLiteral("Cockatrice"); + QString title; + + switch (remoteClient->getStatus()) { + case StatusConnecting: { + title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName()); + break; + } + case StatusRegistering: { + title = appName + " - " + + tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName()); + break; + } + case StatusDisconnected: { + title = appName + " - " + tr("Disconnected"); + break; + } + case StatusLoggingIn: { + title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName()); + break; + } + case StatusLoggedIn: { + title = remoteClient->getUserName() + "@" + remoteClient->peerName(); + break; + } + case StatusRequestingForgotPassword: + case StatusSubmitForgotPasswordChallenge: + case StatusSubmitForgotPasswordReset: + title = appName + " - " + + tr("Requesting forgotten password to %1 as %2...") + .arg(remoteClient->peerName()) + .arg(remoteClient->getUserName()); + break; + default: + title = appName; + } + + emit windowTitleChanged(title); +} + +// static +QString ConnectionController::extractInvalidUsernameMessage(QString &in) +{ + QString out = tr("Invalid username.") + "
"; + QStringList rules = in.split(QChar('|')); + + if (rules.size() == 7 || rules.size() == 9) { + out += tr("Your username must respect these rules:") + "
    "; + + out += "
  • " + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "
  • "; + out += "
  • " + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) + + "
  • "; + out += "
  • " + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) + + "
  • "; + out += + "
  • " + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "
  • "; + + if (rules.at(6).size() > 0) { + out += "
  • " + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "
  • "; + } + + out += "
  • " + + tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) + + "
  • "; + + if (rules.size() == 9) { + if (rules.at(7).size() > 0) { + QString words = rules.at(7).toHtmlEscaped(); + if (words.startsWith("\n")) { + out += tr("no unacceptable language as specified by these server rules:", + "note that the following lines will not be translated"); + for (QString &line : words.split("\n", Qt::SkipEmptyParts)) { + out += "
  • " + line + "
  • "; + } + } else { + out += "
  • " + tr("can not contain any of the following words: %1").arg(words) + "
  • "; + } + } + + if (rules.at(8).size() > 0) { + out += "
  • " + + tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) + + "
  • "; + } + } + + out += "
"; + } else { + out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username."); + } + + return out; +} \ No newline at end of file diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h new file mode 100644 index 000000000..7486bc81a --- /dev/null +++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h @@ -0,0 +1,98 @@ +#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H +#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H + +#include "abstract_client.h" + +#include +#include +#include +#include +#include +#include + +class RemoteClient; +class ServerInfo_User; +class DlgConnect; + +/** + * Owns the RemoteClient and its worker thread. + * Encapsulates all connection, authentication, and registration logic so that + * MainWindow only needs to react to high-level signals. + */ +class ConnectionController : public QObject +{ + Q_OBJECT + +public: + explicit ConnectionController(QWidget *dialogParent, QObject *parent = nullptr); + ~ConnectionController() override; + + RemoteClient *client() const + { + return remoteClient; + } + + void registerToServer(); + void forgotPasswordRequest(); + void connectToServer(); + void + connectToServerDirect(const QString &host, unsigned int port, const QString &playerName, const QString &password); + void disconnectFromServer(); + + void refreshWindowTitle() + { + updateWindowTitle(); + } + +signals: + void windowTitleChanged(const QString &title); + + void tabSupervisorStartRequested(const ServerInfo_User &info); + void tabSupervisorStopRequested(); + + // Passes the raw ClientStatus through so MainWindow can drive its own + // action enable/disable logic + void statusChanged(ClientStatus status); + +private slots: + // Slots wired directly to RemoteClient signals + void onStatusChanged(ClientStatus status); + void onUserInfoReceived(const ServerInfo_User &info); + void onLoginError(int r, QString reasonStr, quint32 endTime, const QList &missingFeatures); + void onRegisterAccepted(); + void onRegisterAcceptedNeedsActivate(); + void onRegisterError(int r, QString reasonStr, quint32 endTime); + void onActivateAccepted(); + void onActivateError(); + void onProtocolVersionMismatch(int localVersion, int remoteVersion); + void onNotifyUserAboutUpdate(); + void onConnectionClosedEvent(const Event_ConnectionClosed &event); + void onServerShutdownEvent(const Event_ServerShutdown &event); + void onSocketError(const QString &errorStr); + void onServerTimeout(); + + // Forgot-password flow + void onForgotPasswordSuccess(); + void onForgotPasswordError(); + void onPromptForgotPasswordReset(); + void onPromptForgotPasswordChallenge(); + +private: + void wireClientSignals(); + void updateWindowTitle(); + + /** Parse the server's pipe-delimited username-rule string into HTML. */ + static QString extractInvalidUsernameMessage(QString &in); + + RemoteClient *remoteClient{nullptr}; + QThread *clientThread{nullptr}; + QWidget *dialogParent{nullptr}; // used as parent for QMessageBox / dialog calls + + // Persistent so it can be updated in-place by onServerShutdownEvent + QMessageBox serverShutdownMessageBox; + + // Kept as a member so the forgot-password signal can be wired to it + DlgConnect *dlgConnect{nullptr}; +}; + +#endif // COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index 0298daa6b..8689a19e9 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -6,12 +6,12 @@ #include #include #include +#include #include #include #include -DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent) - : QObject(parent), cardDatabase(_cardDatabase) +DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent) { manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished); @@ -70,8 +70,8 @@ void DeckStatsInterface::analyzeDeck(const DeckList &deck) void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination) { - auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { - CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); + auto copyIfNotAToken = [&destination](const auto node, const auto card) { + CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); if (dbCard && !dbCard->getIsToken()) { DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1); addedCard->setNumber(card->getNumber()); diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h index 7dc841027..09bf998de 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -1,13 +1,12 @@ /** * @file deck_stats_interface.h * @ingroup ApiInterfaces - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECKSTATS_INTERFACE_H #define DECKSTATS_INTERFACE_H -#include #include class QByteArray; @@ -21,8 +20,6 @@ class DeckStatsInterface : public QObject private: QNetworkAccessManager *manager; - CardDatabase &cardDatabase; - /** * Deckstats doesn't recognize token cards, and instead tries to find the * closest non-token card instead. So we construct a new deck which has no @@ -35,7 +32,7 @@ private slots: void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: - explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); + explicit DeckStatsInterface(QObject *parent = nullptr); void analyzeDeck(const DeckList &deck); }; diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index a30a7f531..5dc77fa2c 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -6,12 +6,12 @@ #include #include #include +#include #include #include #include -TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent) - : QObject(parent), cardDatabase(_cardDatabase) +TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent) { manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished); @@ -89,22 +89,26 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck) QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + // we interpret the redirect and open it in the browser instead, do not follow redirects + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); manager->post(request, data); } void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard) { - auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) { - CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); - if (!dbCard || dbCard->getIsToken()) + auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) { + CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); + if (!dbCard || dbCard->getIsToken()) { return; + } DecklistCardNode *addedCard; - if (node->getName() == DECK_ZONE_SIDE) + if (node->getName() == DECK_ZONE_SIDE) { addedCard = sideboard.addCard(card->getName(), node->getName(), -1); - else + } else { addedCard = mainboard.addCard(card->getName(), node->getName(), -1); + } addedCard->setNumber(card->getNumber()); }; diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h index 0ea9c8358..32f9369d5 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -1,14 +1,14 @@ /** * @file tapped_out_interface.h * @ingroup ApiInterfaces - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAPPEDOUT_INTERFACE_H #define TAPPEDOUT_INTERFACE_H -#include -#include +#include +#include inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface"); @@ -29,14 +29,13 @@ class TappedOutInterface : public QObject private: QNetworkAccessManager *manager; - CardDatabase &cardDatabase; void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); private slots: void queryFinished(QNetworkReply *reply); void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: - explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); + explicit TappedOutInterface(QObject *parent = nullptr); void analyzeDeck(const DeckList &deck); }; diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h index 1e2372fd1..bbdd70c97 100644 --- a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h +++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h @@ -1,8 +1,8 @@ /** * @file deck_link_to_api_transformer.h * @ingroup ApiInterfaces - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_LINK_TO_API_TRANSFORMER_H #define DECK_LINK_TO_API_TRANSFORMER_H diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h index 1818aa35c..87fde4d54 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -1,8 +1,8 @@ /** * @file interface_json_deck_parser.h * @ingroup ApiInterfaces - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef INTERFACE_JSON_DECK_PARSER_H #define INTERFACE_JSON_DECK_PARSER_H diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h index a2feb5ccf..03d4897a2 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h @@ -1,8 +1,8 @@ /** * @file spoiler_background_updater.h * @ingroup Client - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_SPOILER_DOWNLOADER_H #define COCKATRICE_SPOILER_DOWNLOADER_H diff --git a/cockatrice/src/client/network/update/client/client_update_checker.h b/cockatrice/src/client/network/update/client/client_update_checker.h index 4e6f279c3..1a89de533 100644 --- a/cockatrice/src/client/network/update/client/client_update_checker.h +++ b/cockatrice/src/client/network/update/client/client_update_checker.h @@ -1,8 +1,8 @@ /** * @file client_update_checker.h * @ingroup ClientUpdate - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CLIENT_UPDATE_CHECKER_H #define CLIENT_UPDATE_CHECKER_H diff --git a/cockatrice/src/client/network/update/client/release_channel.cpp b/cockatrice/src/client/network/update/client/release_channel.cpp index 9edfe7caf..260167bc8 100644 --- a/cockatrice/src/client/network/update/client/release_channel.cpp +++ b/cockatrice/src/client/network/update/client/release_channel.cpp @@ -129,8 +129,9 @@ void StableReleaseChannel::releaseListFinished() return; } - if (!lastRelease) + if (!lastRelease) { lastRelease = new Release; + } lastRelease->setName(resultMap["name"].toString()); lastRelease->setDescriptionUrl(resultMap["html_url"].toString()); @@ -246,8 +247,9 @@ void BetaReleaseChannel::releaseListFinished() return; } - if (lastRelease == nullptr) + if (lastRelease == nullptr) { lastRelease = new Release; + } lastRelease->setCommitHash(resultMap["target_commitish"].toString()); lastRelease->setPublishDate(resultMap["published_at"].toDate()); diff --git a/cockatrice/src/client/network/update/client/release_channel.h b/cockatrice/src/client/network/update/client/release_channel.h index 93e6b985d..c56d0cfce 100644 --- a/cockatrice/src/client/network/update/client/release_channel.h +++ b/cockatrice/src/client/network/update/client/release_channel.h @@ -1,8 +1,8 @@ /** * @file release_channel.h * @ingroup ClientUpdate - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef RELEASECHANNEL_H #define RELEASECHANNEL_H diff --git a/cockatrice/src/client/network/update/client/update_downloader.cpp b/cockatrice/src/client/network/update/client/update_downloader.cpp index c0f3e945c..a71bcf8f8 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.cpp +++ b/cockatrice/src/client/network/update/client/update_downloader.cpp @@ -10,8 +10,9 @@ UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response( void UpdateDownloader::beginDownload(QUrl downloadUrl) { // Save the original URL because we need it for the filename - if (originalUrl.isEmpty()) + if (originalUrl.isEmpty()) { originalUrl = downloadUrl; + } response = netMan->get(QNetworkRequest(downloadUrl)); connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished); @@ -21,8 +22,9 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl) void UpdateDownloader::downloadError(QNetworkReply::NetworkError) { - if (response == nullptr) + if (response == nullptr) { return; + } emit error(response->errorString().toUtf8()); } diff --git a/cockatrice/src/client/network/update/client/update_downloader.h b/cockatrice/src/client/network/update/client/update_downloader.h index d70759038..9a0caccbc 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.h +++ b/cockatrice/src/client/network/update/client/update_downloader.h @@ -1,8 +1,8 @@ /** * @file update_downloader.h * @ingroup ClientUpdate - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_UPDATEDOWNLOADER_H #define COCKATRICE_UPDATEDOWNLOADER_H diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index a66897b4a..73e5a98a1 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -1,5 +1,7 @@ #include "cache_settings.h" +#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h" +#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h" #include "../network/update/client/release_channel.h" #include "card_counter_settings.h" #include "version_string.h" @@ -24,10 +26,11 @@ SettingsCache &SettingsCache::instance() QString SettingsCache::getDataPath() { - if (isPortableBuild) + if (isPortableBuild) { return qApp->applicationDirPath() + "/data"; - else + } else { return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + } } QString SettingsCache::getSettingsPath() @@ -37,10 +40,11 @@ QString SettingsCache::getSettingsPath() QString SettingsCache::getCachePath() const { - if (isPortableBuild) + if (isPortableBuild) { return qApp->applicationDirPath() + "/cache"; - else + } else { return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + } } QString SettingsCache::getNetworkCachePath() const @@ -50,14 +54,17 @@ QString SettingsCache::getNetworkCachePath() const void SettingsCache::translateLegacySettings() { - if (isPortableBuild) + if (isPortableBuild) { return; + } // Layouts QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini"); - if (layoutFile.exists()) - if (layoutFile.copy(getSettingsPath() + "layouts.ini")) + if (layoutFile.exists()) { + if (layoutFile.copy(getSettingsPath() + "layouts.ini")) { layoutFile.remove(); + } + } QStringList usedKeys; QSettings legacySetting; @@ -116,10 +123,11 @@ void SettingsCache::translateLegacySettings() gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool()); gameFilters().setMinPlayers(legacySetting.value("min_players").toInt()); - if (legacySetting.value("max_players").toInt() > 1) + if (legacySetting.value("max_players").toInt() > 1) { gameFilters().setMaxPlayers(legacySetting.value("max_players").toInt()); - else + } else { gameFilters().setMaxPlayers(99); // This prevents a bug where no games will show if max was not set before + } QStringList allFilters = legacySetting.allKeys(); for (int i = 0; i < allFilters.size(); ++i) { @@ -135,8 +143,9 @@ void SettingsCache::translateLegacySettings() QStringList allLegacyKeys = legacySetting.allKeys(); for (int i = 0; i < allLegacyKeys.size(); ++i) { - if (usedKeys.contains(allLegacyKeys.at(i))) + if (usedKeys.contains(allLegacyKeys.at(i))) { continue; + } settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i))); } } @@ -147,8 +156,9 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat // if the config settings is empty or refers to a not-existing folder, // ensure that the defaut path exists and return it if (tmp.isEmpty() || !QDir(tmp).exists()) { - if (!QDir().mkpath(defaultPath)) + if (!QDir().mkpath(defaultPath)) { qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath; + } tmp = defaultPath; } return tmp; @@ -159,8 +169,9 @@ QString SettingsCache::getSafeConfigFilePath(QString configEntry, QString defaul QString tmp = settings->value(configEntry).toString(); // if the config settings is empty or refers to a not-existing file, // return the default Path - if (!QFile::exists(tmp) || tmp.isEmpty()) + if (!QFile::exists(tmp) || tmp.isEmpty()) { tmp = std::move(defaultPath); + } return tmp; } @@ -168,8 +179,9 @@ SettingsCache::SettingsCache() { // first, figure out if we are running in portable mode isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat"); - if (isPortableBuild) + if (isPortableBuild) { qCInfo(SettingsCacheLog) << "Portable mode enabled"; + } // define a dummy context that will be used where needed QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); @@ -189,8 +201,9 @@ SettingsCache::SettingsCache() cardCounterSettings = new CardCounterSettings(settingsPath, this); - if (!QFile(settingsPath + "global.ini").exists()) + if (!QFile(settingsPath + "global.ini").exists()) { translateLegacySettings(); + } // updates - don't reorder them or their index in the settings won't match // append channels one by one, or msvc will add them in the wrong order. @@ -211,6 +224,7 @@ SettingsCache::SettingsCache() startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool(); cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt(); lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); + alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); @@ -256,14 +270,26 @@ SettingsCache::SettingsCache() settings->setValue("personal/pixmapCacheSize", pixmapCacheSize); settings->setValue("personal/picturedownloadhq", false); settings->setValue("revert/pixmapCacheSize", true); - } else + } else { pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt(); + } // sanity check - if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) + if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) { pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT; + } networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt(); redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt(); + cardPictureLoaderCacheMethod = + settings + ->value("personal/cardPictureLoaderCacheMethod", + static_cast(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE)) + .toInt(); + localCardImageStorageNamingScheme = + settings + ->value("personal/localCardImageStorageNamingScheme", + static_cast(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector)) + .toInt(); picDownload = settings->value("personal/picturedownload", true).toBool(); showStatusBar = settings->value("personal/showStatusBar", false).toBool(); @@ -362,6 +388,7 @@ SettingsCache::SettingsCache() ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool(); + ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool(); scaleCards = settings->value("cards/scaleCards", true).toBool(); verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt(); @@ -769,8 +796,9 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize) void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards) { - if (includeRebalancedCards == _includeRebalancedCards) + if (includeRebalancedCards == _includeRebalancedCards) { return; + } includeRebalancedCards = _includeRebalancedCards; settings->setValue("cards/includerebalancedcards", includeRebalancedCards); @@ -1090,6 +1118,12 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages); } +void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages) +{ + ignoreNonBuddyUserMessages = static_cast(_ignoreNonBuddyUserMessages); + settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages); +} + void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) { pixmapCacheSize = _pixmapCacheSize; @@ -1097,6 +1131,13 @@ void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) emit pixmapCacheSizeChanged(pixmapCacheSize); } +void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod) +{ + cardPictureLoaderCacheMethod = static_cast(_cardImageCachingMethod); + settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod); + emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod); +} + void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize) { networkCacheSize = _networkCacheSize; @@ -1111,6 +1152,14 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl) emit redirectCacheTtlChanged(redirectCacheTtl); } +void SettingsCache::setLocalCardImageStorageNamingScheme( + const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme) +{ + localCardImageStorageNamingScheme = static_cast(_localCardImageStorageNamingScheme); + settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme); + emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme); +} + void SettingsCache::setClientID(const QString &_clientID) { clientID = _clientID; @@ -1246,6 +1295,12 @@ void SettingsCache::setLastCardUpdateCheck(QDate value) settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck); } +void SettingsCache::setAlwaysEnableNewSets(bool value) +{ + alwaysEnableNewSets = value; + settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets); +} + void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) { rememberGameSettings = _rememberGameSettings; @@ -1303,8 +1358,9 @@ void SettingsCache::setMaxFontSize(int _max) void SettingsCache::setRoundCardCorners(bool _roundCardCorners) { - if (_roundCardCorners == roundCardCorners) + if (_roundCardCorners == roundCardCorners) { return; + } roundCardCorners = _roundCardCorners; settings->setValue("cards/roundcardcorners", _roundCardCorners); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index ece61487f..8ee372766 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -1,12 +1,14 @@ /** * @file cache_settings.h * @ingroup Settings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SETTINGSCACHE_H #define SETTINGSCACHE_H +#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h" +#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h" #include "shortcuts_settings.h" #include @@ -181,9 +183,12 @@ signals: void soundThemeChanged(); void ignoreUnregisteredUsersChanged(); void ignoreUnregisteredUserMessagesChanged(); + void ignoreNonBuddyUserMessagesChanged(); void pixmapCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs); void redirectCacheTtlChanged(int newTtl); + void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod); + void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme); void masterVolumeChanged(int value); void chatMentionCompleterChanged(); void downloadSpoilerTimeIndexChanged(); @@ -216,6 +221,7 @@ private: bool checkCardUpdatesOnStartup; int cardUpdateCheckInterval; QDate lastCardUpdateCheck; + bool alwaysEnableNewSets; bool notifyAboutUpdates; bool notifyAboutNewVersion; bool showTipsOnStartup; @@ -289,6 +295,7 @@ private: QString soundThemeName; bool ignoreUnregisteredUsers; bool ignoreUnregisteredUserMessages; + bool ignoreNonBuddyUserMessages; QString picUrl; QString picUrlFallback; QString clientID; @@ -302,6 +309,8 @@ private: int pixmapCacheSize; int networkCacheSize; int redirectCacheTtl; + int cardPictureLoaderCacheMethod; + int localCardImageStorageNamingScheme; bool scaleCards; int verticalCardOverlapPercent; bool showMessagePopups; @@ -502,6 +511,10 @@ public: return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() && getLastCardUpdateCheck() != QDateTime::currentDateTime().date(); } + [[nodiscard]] bool getAlwaysEnableNewSets() const + { + return alwaysEnableNewSets; + } [[nodiscard]] bool getNotifyAboutUpdates() const override { return notifyAboutUpdates; @@ -777,10 +790,18 @@ public: { return ignoreUnregisteredUserMessages; } + [[nodiscard]] bool getIgnoreNonBuddyUserMessages() const + { + return ignoreNonBuddyUserMessages; + } [[nodiscard]] int getPixmapCacheSize() const { return pixmapCacheSize; } + [[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const + { + return static_cast(cardPictureLoaderCacheMethod); + } [[nodiscard]] int getNetworkCacheSizeInMB() const { return networkCacheSize; @@ -789,6 +810,10 @@ public: { return redirectCacheTtl; } + [[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const + { + return static_cast(localCardImageStorageNamingScheme); + } [[nodiscard]] bool getScaleCards() const { return scaleCards; @@ -1092,9 +1117,13 @@ public slots: void setSoundThemeName(const QString &_soundThemeName); void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers); void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); + void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages); void setPixmapCacheSize(const int _pixmapCacheSize); + void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod); void setNetworkCacheSizeInMB(const int _networkCacheSize); void setNetworkRedirectCacheTtl(const int _redirectCacheTtl); + void setLocalCardImageStorageNamingScheme( + const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme); void setCardScaling(const QT_STATE_CHANGED_T _scaleCards); void setStackCardOverlapPercent(const int _verticalCardOverlapPercent); void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups); @@ -1125,6 +1154,7 @@ public slots: void setStartupCardUpdateCheckAlwaysUpdate(bool value); void setCardUpdateCheckInterval(int value); void setLastCardUpdateCheck(QDate value); + void setAlwaysEnableNewSets(bool value); void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate); void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion); void setUpdateReleaseChannelIndex(int value); diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp index 399365c99..662ae0c7d 100644 --- a/cockatrice/src/client/settings/card_counter_settings.cpp +++ b/cockatrice/src/client/settings/card_counter_settings.cpp @@ -15,8 +15,9 @@ void CardCounterSettings::setColor(int counterId, const QColor &color) QString key = QString("cards/counters/%1/color").arg(counterId); - if (settings.value(key).value() == color) + if (settings.value(key).value() == color) { return; + } settings.setValue(key, color); emit colorChanged(counterId, color); diff --git a/cockatrice/src/client/settings/card_counter_settings.h b/cockatrice/src/client/settings/card_counter_settings.h index b1d467d67..2ac658177 100644 --- a/cockatrice/src/client/settings/card_counter_settings.h +++ b/cockatrice/src/client/settings/card_counter_settings.h @@ -1,8 +1,8 @@ /** * @file card_counter_settings.h * @ingroup GameSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_COUNTER_SETTINGS_H #define CARD_COUNTER_SETTINGS_H diff --git a/cockatrice/src/client/settings/shortcut_treeview.h b/cockatrice/src/client/settings/shortcut_treeview.h index 8d74a6f1e..afaf7e7ed 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.h +++ b/cockatrice/src/client/settings/shortcut_treeview.h @@ -1,8 +1,8 @@ /** * @file shortcut_treeview.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SHORTCUT_TREEVIEW_H #define SHORTCUT_TREEVIEW_H diff --git a/cockatrice/src/client/settings/shortcuts_settings.cpp b/cockatrice/src/client/settings/shortcuts_settings.cpp index 53c6f6a23..2ff65ab6f 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.cpp +++ b/cockatrice/src/client/settings/shortcuts_settings.cpp @@ -64,8 +64,13 @@ ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *paren } } -/// PR 5079 changes Textbox/unfocusTextBox to Player/unfocusTextBox and tab_game/aFocusChat to Player/aFocusChat. -/// A migration is necessary to let players keep their already configured shortcuts. +/** + * @brief Migrates legacy shortcut key names to current naming scheme. + * + * PR 5079 changed Textbox/unfocusTextBox to Player/unfocusTextBox and + * tab_game/aFocusChat to Player/aFocusChat. This migration allows players + * to keep their already configured shortcuts. + */ void ShortcutsSettings::migrateShortcuts() { if (QFile(settingsFilePath).exists()) { @@ -236,9 +241,7 @@ bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) c return findOverlaps(name, sequences).isEmpty(); } -/** - * Checks if the shortcut is a shortcut that is active in all windows - */ +/** @brief Checks if the shortcut is a shortcut that is active in all windows. */ static bool isAlwaysActiveShortcut(const QString &shortcutName) { return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs"); diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 1de73c165..45e2c4fca 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -1,8 +1,8 @@ /** * @file shortcuts_settings.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SHORTCUTSSETTINGS_H #define SHORTCUTSSETTINGS_H @@ -537,6 +537,9 @@ private: {"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."), parseSequenceString("Alt+N"), ShortcutGroup::Playing_Area)}, + {"Player/aReduceLifeByPower", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reduce Life by Power"), + parseSequenceString("Ctrl+Shift+L"), + ShortcutGroup::Playing_Area)}, {"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"), parseSequenceString("Ctrl+A"), ShortcutGroup::Playing_Area)}, @@ -664,6 +667,9 @@ private: {"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."), parseSequenceString("Ctrl+I"), ShortcutGroup::Gameplay)}, + {"Player/aFlipCoin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Flip Coin"), + parseSequenceString("Ctrl+Shift+I"), + ShortcutGroup::Gameplay)}, {"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"), parseSequenceString("Ctrl+S"), ShortcutGroup::Gameplay)}, diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp index 31cb2a35e..e592b5ea0 100644 --- a/cockatrice/src/client/sound_engine.cpp +++ b/cockatrice/src/client/sound_engine.cpp @@ -105,8 +105,9 @@ QStringMap &SoundEngine::getAvailableThemes() dir.setPath(SettingsCache::instance().getDataPath() + "/sounds"); for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { - if (!availableThemes.contains(themeName)) + if (!availableThemes.contains(themeName)) { availableThemes.insert(themeName, dir.absoluteFilePath(themeName)); + } } // load themes from cockatrice system dir @@ -121,8 +122,9 @@ QStringMap &SoundEngine::getAvailableThemes() ); for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { - if (!availableThemes.contains(themeName)) + if (!availableThemes.contains(themeName)) { availableThemes.insert(themeName, dir.absoluteFilePath(themeName)); + } } return availableThemes; diff --git a/cockatrice/src/client/sound_engine.h b/cockatrice/src/client/sound_engine.h index f45cccf7d..0c201523a 100644 --- a/cockatrice/src/client/sound_engine.h +++ b/cockatrice/src/client/sound_engine.h @@ -1,8 +1,8 @@ /** * @file sound_engine.h * @ingroup Core - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SOUNDENGINE_H #define SOUNDENGINE_H diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 6b671831d..dd873cfa5 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -88,20 +88,27 @@ static void setupParserRules() const auto arg = std::any_cast(sv[1]); const auto op = std::any_cast(sv[0]); - if (op == ">") + if (op == ">") { return [=](const int s) { return s > arg; }; - if (op == ">=") + } + if (op == ">=") { return [=](const int s) { return s >= arg; }; - if (op == "<") + } + if (op == "<") { return [=](const int s) { return s < arg; }; - if (op == "<=") + } + if (op == "<=") { return [=](const int s) { return s <= arg; }; - if (op == "=") + } + if (op == "=") { return [=](const int s) { return s == arg; }; - if (op == ":") + } + if (op == ":") { return [=](const int s) { return s == arg; }; - if (op == "!=") + } + if (op == "!=") { return [=](const int s) { return s != arg; }; + } return [](int) { return false; }; }; diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h index 1b43d770d..916b629ee 100644 --- a/cockatrice/src/filters/deck_filter_string.h +++ b/cockatrice/src/filters/deck_filter_string.h @@ -1,8 +1,8 @@ /** * @file deck_filter_string.h * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_FILTER_STRING_H #define DECK_FILTER_STRING_H diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp index 7178ce95a..785f753e7 100644 --- a/cockatrice/src/filters/filter_builder.cpp +++ b/cockatrice/src/filters/filter_builder.cpp @@ -11,13 +11,15 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent) { filterCombo = new QComboBox; filterCombo->setObjectName("filterCombo"); - for (int i = 0; i < CardFilter::AttrEnd; i++) + for (int i = 0; i < CardFilter::AttrEnd; i++) { filterCombo->addItem(CardFilter::attrName(static_cast(i)), QVariant(i)); + } typeCombo = new QComboBox; typeCombo->setObjectName("typeCombo"); - for (int i = 0; i < CardFilter::TypeEnd; i++) + for (int i = 0; i < CardFilter::TypeEnd; i++) { typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i)); + } QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString()); ok->setObjectName("ok"); @@ -53,8 +55,9 @@ FilterBuilder::~FilterBuilder() void FilterBuilder::destroyFilter() { - if (fltr) + if (fltr) { delete fltr; + } } static int comboCurrentIntData(const QComboBox *combo) @@ -67,8 +70,9 @@ void FilterBuilder::emit_add() QString txt; txt = edit->text(); - if (txt.length() < 1) + if (txt.length() < 1) { return; + } destroyFilter(); fltr = new CardFilter(txt, static_cast(comboCurrentIntData(typeCombo)), diff --git a/cockatrice/src/filters/filter_builder.h b/cockatrice/src/filters/filter_builder.h index 74872e3cd..98fe3fd02 100644 --- a/cockatrice/src/filters/filter_builder.h +++ b/cockatrice/src/filters/filter_builder.h @@ -1,8 +1,8 @@ /** * @file filter_builder.h * @ingroup CardDatabaseModelFilters - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef FILTERBUILDER_H #define FILTERBUILDER_H diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp index f0a02ec79..33b54530e 100644 --- a/cockatrice/src/filters/filter_tree_model.cpp +++ b/cockatrice/src/filters/filter_tree_model.cpp @@ -23,8 +23,9 @@ void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i) int idx; idx = node->index(); - if (idx >= 0) + if (idx >= 0) { beginInsertRows(createIndex(idx, 0, (void *)node), i, i); + } } void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int) @@ -32,8 +33,9 @@ void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int) int idx; idx = node->index(); - if (idx >= 0) + if (idx >= 0) { endInsertRows(); + } } void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i) @@ -41,8 +43,9 @@ void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i) int idx; idx = node->index(); - if (idx >= 0) + if (idx >= 0) { beginRemoveRows(createIndex(idx, 0, (void *)node), i, i); + } } void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int) @@ -50,8 +53,9 @@ void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int) int idx; idx = node->index(); - if (idx >= 0) + if (idx >= 0) { endRemoveRows(); + } } FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const @@ -59,12 +63,14 @@ FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const void *ip; FilterTreeNode *node; - if (!idx.isValid()) + if (!idx.isValid()) { return fTree; + } ip = idx.internalPointer(); - if (ip == NULL) + if (ip == NULL) { return fTree; + } node = static_cast(ip); return node; @@ -145,14 +151,16 @@ int FilterTreeModel::rowCount(const QModelIndex &parent) const const FilterTreeNode *node; int result; - if (parent.column() > 0) + if (parent.column() > 0) { return 0; + } node = indexToNode(parent); - if (node) + if (node) { result = node->childCount(); - else + } else { result = 0; + } return result; } @@ -166,14 +174,17 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const { const FilterTreeNode *node; - if (!index.isValid()) + if (!index.isValid()) { return QVariant(); - if (index.column() >= columnCount()) + } + if (index.column() >= columnCount()) { return QVariant(); + } node = indexToNode(index); - if (node == NULL) + if (node == NULL) { return QVariant(); + } switch (role) { case Qt::FontRole: @@ -190,10 +201,11 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const case Qt::WhatsThisRole: return node->text(); case Qt::CheckStateRole: - if (node->isEnabled()) + if (node->isEnabled()) { return Qt::Checked; - else + } else { return Qt::Unchecked; + } default: return QVariant(); } @@ -205,22 +217,27 @@ bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, i { FilterTreeNode *node; - if (!index.isValid()) + if (!index.isValid()) { return false; - if (index.column() >= columnCount()) + } + if (index.column() >= columnCount()) { return false; - if (role != Qt::CheckStateRole) + } + if (role != Qt::CheckStateRole) { return false; + } node = indexToNode(index); - if (node == NULL || node == fTree) + if (node == NULL || node == fTree) { return false; + } Qt::CheckState state = static_cast(value.toInt()); - if (state == Qt::Checked) + if (state == Qt::Checked) { node->enable(); - else + } else { node->disable(); + } emit dataChanged(index, index); return true; @@ -231,16 +248,19 @@ Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const const FilterTreeNode *node; Qt::ItemFlags result; - if (!index.isValid()) + if (!index.isValid()) { return Qt::NoItemFlags; + } node = indexToNode(index); - if (node == NULL) + if (node == NULL) { return Qt::NoItemFlags; + } result = Qt::ItemIsEnabled; - if (node == fTree) + if (node == fTree) { return result; + } result |= Qt::ItemIsSelectable; result |= Qt::ItemIsUserCheckable; @@ -252,8 +272,9 @@ QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int { FilterTreeNode *child; - if (column > 0 || row >= node->childCount()) + if (column > 0 || row >= node->childCount()) { return QModelIndex(); + } child = node->nodeAt(row); return createIndex(row, column, child); @@ -263,12 +284,14 @@ QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &paren { const FilterTreeNode *node; - if (!hasIndex(row, column, parent)) + if (!hasIndex(row, column, parent)) { return QModelIndex(); + } node = indexToNode(parent); - if (node == NULL) + if (node == NULL) { return QModelIndex(); + } return nodeIndex(node, row, column); } @@ -279,18 +302,21 @@ QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const FilterTreeNode *parent; QModelIndex idx; - if (!ind.isValid()) + if (!ind.isValid()) { return QModelIndex(); + } node = indexToNode(ind); - if (node == NULL || node == fTree) + if (node == NULL || node == fTree) { return QModelIndex(); + } parent = node->parent(); if (parent) { int row = parent->index(); - if (row < 0) + if (row < 0) { return QModelIndex(); + } idx = createIndex(row, 0, parent); return idx; } @@ -304,18 +330,22 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent) int i, last; last = row + count - 1; - if (!parent.isValid() || count < 1 || row < 0) + if (!parent.isValid() || count < 1 || row < 0) { return false; + } node = indexToNode(parent); - if (node == NULL || last >= node->childCount()) + if (node == NULL || last >= node->childCount()) { return false; + } - for (i = 0; i < count; i++) + for (i = 0; i < count; i++) { node->deleteAt(row); + } - if (node != fTree && node->childCount() < 1) + if (node != fTree && node->childCount() < 1) { return removeRow(parent.row(), parent.parent()); + } return true; } diff --git a/cockatrice/src/filters/filter_tree_model.h b/cockatrice/src/filters/filter_tree_model.h index c6666a838..7452f7a61 100644 --- a/cockatrice/src/filters/filter_tree_model.h +++ b/cockatrice/src/filters/filter_tree_model.h @@ -1,8 +1,8 @@ /** * @file filter_tree_model.h * @ingroup CardDatabaseModelFilters - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef FILTERTREEMODEL_H #define FILTERTREEMODEL_H diff --git a/cockatrice/src/filters/syntax_help.h b/cockatrice/src/filters/syntax_help.h index 7e5ef3e0e..d06fe03e5 100644 --- a/cockatrice/src/filters/syntax_help.h +++ b/cockatrice/src/filters/syntax_help.h @@ -2,8 +2,8 @@ * @file syntax_help.h * @ingroup CardDatabaseModelFilters * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SEARCH_SYNTAX_HELP_H #define SEARCH_SYNTAX_HELP_H diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp index 9216f9174..c20003ece 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/cockatrice/src/game/abstract_game.cpp @@ -1,9 +1,9 @@ #include "abstract_game.h" #include "../interface/widgets/tabs/tab_game.h" -#include "player/player.h" +#include "player/player_logic.h" -AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab) +AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent) { gameMetaInfo = new GameMetaInfo(this); gameEventHandler = new GameEventHandler(this); @@ -24,10 +24,11 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const } return gameState->getClients().at(playerId); - } else if (gameState->getClients().isEmpty()) + } else if (gameState->getClients().isEmpty()) { return nullptr; - else + } else { return gameState->getClients().first(); + } } void AbstractGame::loadReplay(GameReplay *replay) @@ -43,13 +44,15 @@ void AbstractGame::setActiveCard(CardItem *card) CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const { - Player *player = playerManager->getPlayer(playerId); - if (!player) + PlayerLogic *player = playerManager->getPlayer(playerId); + if (!player) { return nullptr; + } CardZoneLogic *zone = player->getZones().value(zoneName, 0); - if (!zone) + if (!zone) { return nullptr; + } return zone->getCard(cardId); } \ No newline at end of file diff --git a/cockatrice/src/game/abstract_game.h b/cockatrice/src/game/abstract_game.h index cb4dbac2a..5115ed5ca 100644 --- a/cockatrice/src/game/abstract_game.h +++ b/cockatrice/src/game/abstract_game.h @@ -1,8 +1,8 @@ /** * @file abstract_game.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_ABSTRACT_GAME_H #define COCKATRICE_ABSTRACT_GAME_H @@ -16,26 +16,19 @@ #include class CardItem; -class TabGame; class AbstractGame : public QObject { Q_OBJECT public: - explicit AbstractGame(TabGame *tab); + explicit AbstractGame(QObject *parent); - TabGame *tab; GameMetaInfo *gameMetaInfo; GameState *gameState; GameEventHandler *gameEventHandler; PlayerManager *playerManager; CardItem *activeCard; - TabGame *getTab() const - { - return tab; - } - GameMetaInfo *getGameMetaInfo() { return gameMetaInfo; diff --git a/cockatrice/src/game/arrow_registry.cpp b/cockatrice/src/game/arrow_registry.cpp new file mode 100644 index 000000000..286764b3b --- /dev/null +++ b/cockatrice/src/game/arrow_registry.cpp @@ -0,0 +1,48 @@ +#include "arrow_registry.h" + +#include "../game_graphics/board/arrow_item.h" + +void ArrowRegistry::insert(QSharedPointer data, ArrowItem *arrow) +{ + const ArrowKey key{data->creatorId, data->id}; + + if (auto *existing = take(data->creatorId, data->id)) { + existing->delArrow(); + } + + dataStore.insert(key, data); + items.insert(key, arrow); + byPlayer[data->creatorId].insert(data->id); +} + +ArrowItem *ArrowRegistry::take(int creatorId, int arrowId) +{ + const ArrowKey key{creatorId, arrowId}; + dataStore.remove(key); + auto &playerSet = byPlayer[creatorId]; + playerSet.remove(arrowId); + if (playerSet.isEmpty()) { + byPlayer.remove(creatorId); + } + return items.take(key); +} + +ArrowItem *ArrowRegistry::get(int creatorId, int arrowId) const +{ + return items.value(ArrowKey{creatorId, arrowId}, nullptr); +} + +bool ArrowRegistry::contains(int creatorId, int arrowId) const +{ + return items.contains(ArrowKey{creatorId, arrowId}); +} + +QSet ArrowRegistry::idsForPlayer(int playerId) const +{ + return byPlayer.value(playerId); +} + +QList ArrowRegistry::all() const +{ + return items.values(); +} \ No newline at end of file diff --git a/cockatrice/src/game/arrow_registry.h b/cockatrice/src/game/arrow_registry.h new file mode 100644 index 000000000..ef98229a2 --- /dev/null +++ b/cockatrice/src/game/arrow_registry.h @@ -0,0 +1,43 @@ +#ifndef COCKATRICE_ARROW_REGISTRY_H +#define COCKATRICE_ARROW_REGISTRY_H + +#include "board/arrow_data.h" + +#include +#include +#include + +class ArrowItem; + +struct ArrowKey +{ + int creatorId; + int arrowId; + + bool operator<(const ArrowKey &other) const + { + if (creatorId != other.creatorId) { + return creatorId < other.creatorId; + } + return arrowId < other.arrowId; + } +}; + +class ArrowRegistry +{ +public: + void insert(QSharedPointer data, ArrowItem *arrow); + ArrowItem *take(int creatorId, int arrowId); + + [[nodiscard]] ArrowItem *get(int creatorId, int arrowId) const; + [[nodiscard]] bool contains(int creatorId, int arrowId) const; + [[nodiscard]] QSet idsForPlayer(int playerId) const; + [[nodiscard]] QList all() const; + +private: + QMap> dataStore; + QMap items; + QMap> byPlayer; +}; + +#endif \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_data.cpp b/cockatrice/src/game/board/arrow_data.cpp new file mode 100644 index 000000000..9e89deed0 --- /dev/null +++ b/cockatrice/src/game/board/arrow_data.cpp @@ -0,0 +1,21 @@ +#include "arrow_data.h" + +ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator) +{ + ArrowData data; + data.creatorId = creatorId; + data.isLocalCreator = isLocalCreator; + data.id = arrow.id(); + data.startPlayerId = arrow.start_player_id(); + data.startZone = QString::fromStdString(arrow.start_zone()); + data.startCardId = arrow.start_card_id(); + data.targetPlayerId = arrow.target_player_id(); + data.color = convertColorToQColor(arrow.arrow_color()); + + if (arrow.has_target_zone()) { + data.targetZone = QString::fromStdString(arrow.target_zone()); + data.targetCardId = arrow.target_card_id(); + } + + return data; +} \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_data.h b/cockatrice/src/game/board/arrow_data.h new file mode 100644 index 000000000..2752f97e3 --- /dev/null +++ b/cockatrice/src/game/board/arrow_data.h @@ -0,0 +1,30 @@ +#ifndef COCKATRICE_ARROW_DATA_H +#define COCKATRICE_ARROW_DATA_H + +#include +#include +#include +#include + +struct ArrowData +{ + int creatorId = -1; + bool isLocalCreator = false; + int id = -1; + int startPlayerId = -1; + QString startZone = ""; + int startCardId = -1; + int targetPlayerId = -1; + QString targetZone = ""; + int targetCardId = -1; + QColor color = ""; + + static ArrowData fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator); + + bool isPlayerTargeted() const + { + return targetZone.isEmpty(); + } +}; + +#endif // COCKATRICE_ARROW_DATA_H diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp deleted file mode 100644 index 2dbd913fa..000000000 --- a/cockatrice/src/game/board/arrow_target.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "arrow_target.h" - -#include "../player/player.h" -#include "arrow_item.h" - -ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent) - : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) -{ - setFlag(ItemSendsScenePositionChanges); -} - -ArrowTarget::~ArrowTarget() -{ - for (int i = 0; i < arrowsFrom.size(); ++i) { - arrowsFrom[i]->setStartItem(0); - arrowsFrom[i]->delArrow(); - } - for (int i = 0; i < arrowsTo.size(); ++i) { - arrowsTo[i]->setTargetItem(0); - arrowsTo[i]->delArrow(); - } -} - -void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) -{ - beingPointedAt = _beingPointedAt; - update(); -} - -QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) -{ - if (change == ItemScenePositionHasChanged && scene()) { - for (auto *arrow : arrowsFrom) - arrow->updatePath(); - - for (auto *arrow : arrowsTo) - arrow->updatePath(); - } - - return QGraphicsItem::itemChange(change, value); -} diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h deleted file mode 100644 index 55f4ef678..000000000 --- a/cockatrice/src/game/board/arrow_target.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file arrow_target.h - * @ingroup GameGraphics - * @brief TODO: Document this. - */ - -#ifndef ARROWTARGET_H -#define ARROWTARGET_H - -#include "../../game_graphics/board/abstract_graphics_item.h" - -#include - -class Player; -class ArrowItem; - -class ArrowTarget : public AbstractGraphicsItem -{ - Q_OBJECT -protected: - Player *owner; - -private: - bool beingPointedAt; - QList arrowsFrom, arrowsTo; - -public: - explicit ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr); - ~ArrowTarget() override; - - [[nodiscard]] Player *getOwner() const - { - return owner; - } - - void setBeingPointedAt(bool _beingPointedAt); - [[nodiscard]] bool getBeingPointedAt() const - { - return beingPointedAt; - } - - [[nodiscard]] const QList &getArrowsFrom() const - { - return arrowsFrom; - } - void addArrowFrom(ArrowItem *arrow) - { - arrowsFrom.append(arrow); - } - void removeArrowFrom(ArrowItem *arrow) - { - arrowsFrom.removeOne(arrow); - } - [[nodiscard]] const QList &getArrowsTo() const - { - return arrowsTo; - } - void addArrowTo(ArrowItem *arrow) - { - arrowsTo.append(arrow); - } - void removeArrowTo(ArrowItem *arrow) - { - arrowsTo.removeOne(arrow); - } - -protected: - QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; -}; -#endif diff --git a/cockatrice/src/game/board/card_list.cpp b/cockatrice/src/game/board/card_list.cpp index c324ca10a..0080b5ae6 100644 --- a/cockatrice/src/game/board/card_list.cpp +++ b/cockatrice/src/game/board/card_list.cpp @@ -1,6 +1,6 @@ #include "card_list.h" -#include "card_item.h" +#include "../../game_graphics/board/card_item.h" #include #include diff --git a/cockatrice/src/game/board/card_list.h b/cockatrice/src/game/board/card_list.h index 07be33b32..85a6848b7 100644 --- a/cockatrice/src/game/board/card_list.h +++ b/cockatrice/src/game/board/card_list.h @@ -1,8 +1,8 @@ /** * @file card_list.h * @ingroup GameLogicCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDLIST_H #define CARDLIST_H diff --git a/cockatrice/src/game/board/card_state.cpp b/cockatrice/src/game/board/card_state.cpp new file mode 100644 index 000000000..4319400d7 --- /dev/null +++ b/cockatrice/src/game/board/card_state.cpp @@ -0,0 +1,111 @@ +#include "card_state.h" + +void CardState::resetState(bool keepAnnotations) +{ + attacking = false; + counters.clear(); + pt.clear(); + if (!keepAnnotations) { + annotation.clear(); + } + attachedTo = nullptr; +} + +void CardState::setZone(CardZoneLogic *_zone) +{ + if (zone == _zone) { + return; + } + + zone = _zone; + emit zoneChanged(this, zone); + emit stateChanged(); +} + +void CardState::setAttacking(bool _attacking) +{ + if (attacking == _attacking) { + return; + } + attacking = _attacking; + emit attackingChanged(_attacking); + emit stateChanged(); +} + +void CardState::insertCounter(int id, int value) +{ + counters.insert(id, value); + + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::setCounter(int id, int value) +{ + if (value) { + counters[id] = value; + } else { + counters.remove(id); + } + + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::clearCounters() +{ + counters.clear(); + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::setAnnotation(const QString &_annotation) +{ + if (annotation == _annotation) { + return; + } + annotation = _annotation; + emit annotationChanged(annotation); + emit stateChanged(); +} + +void CardState::setPT(const QString &_pt) +{ + if (pt == _pt) { + return; + } + pt = _pt; + emit ptChanged(pt); + emit stateChanged(); +} + +void CardState::setDoesntUntap(bool _doesntUntap) +{ + if (doesntUntap == _doesntUntap) { + return; + } + doesntUntap = _doesntUntap; + emit doesntUntapChanged(_doesntUntap); + emit stateChanged(); +} + +void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange) +{ + if (destroyOnZoneChange == _destroyOnZoneChange) { + return; + } + + destroyOnZoneChange = _destroyOnZoneChange; + emit destroyOnZoneChangeChanged(_destroyOnZoneChange); + emit stateChanged(); +} + +void CardState::setAttachedTo(CardItem *_attachedTo) +{ + if (attachedTo == _attachedTo) { + return; + } + attachedTo = _attachedTo; + emit attachedToChanged(_attachedTo); + emit stateChanged(); +} \ No newline at end of file diff --git a/cockatrice/src/game/board/card_state.h b/cockatrice/src/game/board/card_state.h new file mode 100644 index 000000000..0498b1aa2 --- /dev/null +++ b/cockatrice/src/game/board/card_state.h @@ -0,0 +1,103 @@ +#ifndef COCKATRICE_CARD_STATE_H +#define COCKATRICE_CARD_STATE_H + +#include +#include + +class CardZoneLogic; +class CardItem; +class CardState : public QObject +{ + Q_OBJECT + +private: + bool attacking = false; + QMap counters; + QString annotation; + QString pt; + bool doesntUntap = false; + bool destroyOnZoneChange = false; + + CardItem *attachedTo = nullptr; + CardZoneLogic *zone = nullptr; + +signals: + void stateChanged(); + + void attackingChanged(bool newValue); + void countersChanged(const QMap &newCounters); + void annotationChanged(const QString &newAnnotation); + void ptChanged(const QString &newPt); + void doesntUntapChanged(bool newValue); + void destroyOnZoneChangeChanged(bool newValue); + void attachedToChanged(CardItem *newAttachedTo); + void zoneChanged(CardState *changedCard, CardZoneLogic *newZone); + +public: + explicit CardState(QObject *parent, CardZoneLogic *_zone) : QObject(parent), zone(_zone) + { + } + + void resetState(bool keepAnnotations); + + CardZoneLogic *getZone() const + { + return zone; + } + + void setZone(CardZoneLogic *_zone); + + bool getAttacking() const + { + return attacking; + } + void setAttacking(bool _attacking); + + const QMap &getCounters() const + { + return counters; + } + + void insertCounter(int id, int value); + + void setCounter(int id, int value); + + void clearCounters(); + + QString getAnnotation() const + { + return annotation; + } + + void setAnnotation(const QString &_annotation); + + QString getPT() const + { + return pt; + } + + void setPT(const QString &_pt); + + bool getDoesntUntap() const + { + return doesntUntap; + } + + void setDoesntUntap(bool _doesntUntap); + + bool getDestroyOnZoneChange() const + { + return destroyOnZoneChange; + } + + void setDestroyOnZoneChange(bool _destroyOnZoneChange); + + CardItem *getAttachedTo() const + { + return attachedTo; + } + + void setAttachedTo(CardItem *_attachedTo); +}; + +#endif // COCKATRICE_CARD_STATE_H diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp new file mode 100644 index 000000000..6da18b662 --- /dev/null +++ b/cockatrice/src/game/board/counter_state.cpp @@ -0,0 +1,24 @@ +#include "counter_state.h" + +#include + +CounterState::CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent) + : QObject(parent), id(id), name(name), color(color), radius(radius), value(value) +{ +} + +CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject *parent) +{ + return new CounterState(counter.id(), QString::fromStdString(counter.name()), + convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent); +} + +void CounterState::setValue(int newValue) +{ + if (newValue == value) { + return; + } + int old = value; + value = newValue; + emit valueChanged(old, newValue); +} \ No newline at end of file diff --git a/cockatrice/src/game/board/counter_state.h b/cockatrice/src/game/board/counter_state.h new file mode 100644 index 000000000..0f2f16b55 --- /dev/null +++ b/cockatrice/src/game/board/counter_state.h @@ -0,0 +1,51 @@ +#ifndef COCKATRICE_COUNTER_STATE_H +#define COCKATRICE_COUNTER_STATE_H + +#include +#include +#include +#include + +class CounterState : public QObject +{ + Q_OBJECT +public: + CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent = nullptr); + + static CounterState *fromProto(const ServerInfo_Counter &counter, QObject *parent = nullptr); + + int getId() const + { + return id; + } + QString getName() const + { + return name; + } + QColor getColor() const + { + return color; + } + int getRadius() const + { + return radius; + } + int getValue() const + { + return value; + } + + void setValue(int newValue); + +signals: + void valueChanged(int oldValue, int newValue); + +private: + int id; + QString name; + QColor color; + int radius; + int value; +}; + +#endif // COCKATRICE_COUNTER_STATE_H diff --git a/cockatrice/src/game/game.cpp b/cockatrice/src/game/game.cpp index 38477f7f7..4c8b109c2 100644 --- a/cockatrice/src/game/game.cpp +++ b/cockatrice/src/game/game.cpp @@ -4,16 +4,16 @@ #include -Game::Game(TabGame *_tab, +Game::Game(QObject *_parent, + bool isLocalGame, QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes) - : AbstractGame(_tab) + : AbstractGame(_parent) { gameMetaInfo->setFromProto(event.game_info()); gameMetaInfo->setRoomGameTypes(_roomGameTypes); - gameState = new GameState(this, 0, event.host_id(), tab->getTabSupervisor()->getIsLocalGame(), _clients, false, - event.resuming(), -1, false); + gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator()); gameMetaInfo->setStarted(false); diff --git a/cockatrice/src/game/game.h b/cockatrice/src/game/game.h index 96ebbae4d..4f912664c 100644 --- a/cockatrice/src/game/game.h +++ b/cockatrice/src/game/game.h @@ -1,8 +1,8 @@ /** * @file game.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_GAME_H #define COCKATRICE_GAME_H @@ -16,7 +16,8 @@ class Game : public AbstractGame Q_OBJECT public: - Game(TabGame *tab, + Game(QObject *parent, + bool isLocalGame, QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes); diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index 7bfc4da75..4a96eebdb 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -1,8 +1,8 @@ #include "game_event_handler.h" +#include "../game_graphics/log/message_log_widget.h" #include "../interface/widgets/tabs/tab_game.h" #include "abstract_game.h" -#include "log/message_log_widget.h" #include #include @@ -36,8 +36,9 @@ GameEventHandler::GameEventHandler(AbstractGame *_game) : QObject(_game), game(_ void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId) { AbstractClient *client = game->getClientForPlayer(playerId); - if (!client) + if (!client) { return; + } connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished); client->sendCommand(pend); @@ -46,8 +47,9 @@ void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId) void GameEventHandler::sendGameCommand(const google::protobuf::Message &command, int playerId) { AbstractClient *client = game->getClientForPlayer(playerId); - if (!client) + if (!client) { return; + } PendingCommand *pend = prepareGameCommand(command); connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished); @@ -56,8 +58,9 @@ void GameEventHandler::sendGameCommand(const google::protobuf::Message &command, void GameEventHandler::commandFinished(const Response &response) { - if (response.response_code() == Response::RespChatFlood) + if (response.response_code() == Response::RespChatFlood) { emit gameFlooded(); + } } PendingCommand *GameEventHandler::prepareGameCommand(const ::google::protobuf::Message &cmd) @@ -96,7 +99,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont, if (cont.has_forced_by_judge()) { auto id = cont.forced_by_judge(); - Player *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr); + PlayerLogic *judgep = game->getPlayerManager()->getPlayers().value(id, nullptr); if (judgep) { emit setContextJudgeName(judgep->getPlayerInfo()->getName()); } else if (game->getPlayerManager()->getSpectators().contains(id)) { @@ -117,9 +120,11 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont, break; } } else { - if ((game->getGameState()->getClients().size() > 1) && (playerId != -1)) - if (game->getGameState()->getClients().at(playerId) != client) + if ((game->getGameState()->getClients().size() > 1) && (playerId != -1)) { + if (game->getGameState()->getClients().at(playerId) != client) { continue; + } + } switch (eventType) { case GameEvent::GAME_STATE_CHANGED: @@ -155,7 +160,7 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont, break; default: { - Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0); + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0); if (!player) { qCWarning(GameEventHandlerLog) << "unhandled game event: invalid player id"; break; @@ -208,11 +213,25 @@ void GameEventHandler::handleChatMessageSent(const QString &chatMessage) sendGameCommand(cmd); } -void GameEventHandler::handleArrowDeletion(int arrowId) +void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId) { Command_DeleteArrow cmd; cmd.set_arrow_id(arrowId); - sendGameCommand(cmd); + + auto preparedCommand = prepareGameCommand(cmd); + + connect(preparedCommand, &PendingCommand::finished, this, [creatorId, arrowId, this](const Response &response) { + handleArrowDeletionFinished(response, creatorId, arrowId); + }); + + sendGameCommand(preparedCommand); +} + +void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId) +{ + if (response.response_code() == Response::RespNameNotFound) { + emit arrowDeleted(creatorId, arrowId); + } } void GameEventHandler::eventSpectatorSay(const Event_GameSay &event, @@ -256,7 +275,7 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event emit spectatorJoined(prop); } } else { - Player *player = game->getPlayerManager()->getPlayers().value(playerId, 0); + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0); if (!player) { player = game->getPlayerManager()->addPlayer(playerId, prop.user_info()); emit playerJoined(prop); @@ -284,8 +303,9 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event if (event.game_started() && !game->getGameMetaInfo()->started()) { game->getGameState()->setResuming(!game->getGameState()->isGameStateKnown()); game->getGameMetaInfo()->setStarted(event.game_started()); - if (game->getGameState()->isGameStateKnown()) + if (game->getGameState()->isGameStateKnown()) { emit logGameStart(); + } game->getGameState()->setActivePlayer(event.active_player_id()); game->getGameState()->setCurrentPhase(event.active_phase()); } else if (!event.game_started() && game->getGameMetaInfo()->started()) { @@ -304,9 +324,10 @@ void GameEventHandler::processCardAttachmentsForPlayers(const Event_GameStateCha const ServerInfo_Player &playerInfo = event.player_list(i); const ServerInfo_PlayerProperties &prop = playerInfo.properties(); if (!prop.spectator()) { - Player *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(prop.player_id(), 0); + if (!player) { continue; + } player->processCardAttachment(playerInfo); } } @@ -316,9 +337,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties int eventPlayerId, const GameEventContext &context) { - Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); + if (!player) { return; + } const ServerInfo_PlayerProperties &prop = event.player_properties(); emit playerPropertiesChanged(prop, eventPlayerId); @@ -326,8 +348,9 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties switch (contextType) { case GameEventContext::READY_START: { bool ready = prop.ready_start(); - if (player->getPlayerInfo()->getLocal()) + if (player->getPlayerInfo()->getLocal()) { emit localPlayerReadyStateChanged(player->getPlayerInfo()->getId(), ready); + } if (ready) { emit logReadyStart(player); } else { @@ -338,9 +361,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties case GameEventContext::CONCEDE: { player->setConceded(true); - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) + QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); + while (playerIterator.hasNext()) { playerIterator.next().value()->updateZones(); + } emit logConcede(eventPlayerId); @@ -349,9 +373,10 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties case GameEventContext::UNCONCEDE: { player->setConceded(false); - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) + QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); + while (playerIterator.hasNext()) { playerIterator.next().value()->updateZones(); + } emit logUnconcede(eventPlayerId); @@ -389,15 +414,16 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/, QString playerName = QString::fromStdString(playerInfo.user_info().name()); emit addPlayerToAutoCompleteList(playerName); - if (game->getPlayerManager()->getPlayers().contains(playerId)) + if (game->getPlayerManager()->getPlayers().contains(playerId)) { return; + } if (playerInfo.spectator()) { game->getPlayerManager()->addSpectator(playerId, playerInfo); emit logJoinSpectator(playerName); emit spectatorJoined(playerInfo); } else { - Player *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info()); + PlayerLogic *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info()); emit logJoinPlayer(newPlayer); emit playerJoined(playerInfo); } @@ -425,23 +451,25 @@ QString GameEventHandler::getLeaveReason(Event_Leave::LeaveReason reason) } void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/) { - Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); + if (!player) { return; + } + player->clear(); emit playerLeft(eventPlayerId); emit logLeave(player, getLeaveReason(event.reason())); game->getPlayerManager()->removePlayer(eventPlayerId); - player->clear(); player->deleteLater(); // Rearrange all remaining zones so that attachment relationship updates take place - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) + QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); + while (playerIterator.hasNext()) { playerIterator.next().value()->updateZones(); + } emitUserEvent(); } @@ -460,9 +488,10 @@ void GameEventHandler::eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/) { - Player *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayers().value(eventPlayerId, 0); + if (!player) { return; + } emit logTurnReversed(player, event.reversed()); } @@ -490,9 +519,10 @@ void GameEventHandler::eventSetActivePlayer(const Event_SetActivePlayer &event, const GameEventContext & /*context*/) { game->getGameState()->setActivePlayer(event.active_player_id()); - Player *player = game->getPlayerManager()->getPlayer(event.active_player_id()); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayer(event.active_player_id()); + if (!player) { return; + } emit logActivePlayer(player); emitUserEvent(); } diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h index 302e7a6ff..f47116949 100644 --- a/cockatrice/src/game/game_event_handler.h +++ b/cockatrice/src/game/game_event_handler.h @@ -1,8 +1,8 @@ /** * @file game_event_handler.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_GAME_EVENT_HANDLER_H #define COCKATRICE_GAME_EVENT_HANDLER_H @@ -38,7 +38,7 @@ class Event_Kicked; class Event_ReverseTurn; class AbstractGame; class PendingCommand; -class Player; +class PlayerLogic; inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler"); @@ -60,7 +60,8 @@ public: void handleActivePhaseChanged(int phase); void handleGameLeft(); void handleChatMessageSent(const QString &chatMessage); - void handleArrowDeletion(int arrowId); + void handleArrowDeletion(int creatorId, int arrowId); + void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId); void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); @@ -95,7 +96,7 @@ public slots: signals: void emitUserEvent(); void addPlayerToAutoCompleteList(QString playerName); - void localPlayerDeckSelected(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); + void localPlayerDeckSelected(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo); void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName); void remotePlayersDecksSelected(QVector>> opponentDecks); void localPlayerSideboardLocked(int playerId, bool sideboardLocked); @@ -112,21 +113,22 @@ signals: void containerProcessingStarted(GameEventContext context); void setContextJudgeName(QString judgeName); void containerProcessingDone(); + void arrowDeleted(int creatorId, int arrowId); void logSpectatorSay(ServerInfo_User userInfo, QString message); void logSpectatorLeave(QString name, QString reason); void logGameStart(); - void logReadyStart(Player *player); - void logNotReadyStart(Player *player); - void logDeckSelect(Player *player, QString deckHash, int sideboardSize); - void logSideboardLockSet(Player *player, bool sideboardLocked); - void logConnectionStateChanged(Player *player, bool connected); + void logReadyStart(PlayerLogic *player); + void logNotReadyStart(PlayerLogic *player); + void logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize); + void logSideboardLockSet(PlayerLogic *player, bool sideboardLocked); + void logConnectionStateChanged(PlayerLogic *player, bool connected); void logJoinSpectator(QString spectatorName); - void logJoinPlayer(Player *player); - void logLeave(Player *player, QString reason); + void logJoinPlayer(PlayerLogic *player); + void logLeave(PlayerLogic *player, QString reason); void logKicked(); - void logTurnReversed(Player *player, bool reversed); + void logTurnReversed(PlayerLogic *player, bool reversed); void logGameClosed(); - void logActivePlayer(Player *activePlayer); + void logActivePlayer(PlayerLogic *activePlayer); void logActivePhaseChanged(int activePhase); void logConcede(int playerId); void logUnconcede(int playerId); diff --git a/cockatrice/src/game/game_meta_info.h b/cockatrice/src/game/game_meta_info.h index b5f5bfe4f..cdba1605f 100644 --- a/cockatrice/src/game/game_meta_info.h +++ b/cockatrice/src/game/game_meta_info.h @@ -1,8 +1,8 @@ /** * @file game_meta_info.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef GAME_META_INFO_H #define GAME_META_INFO_H @@ -87,15 +87,17 @@ public: public slots: void setStarted(bool s) { - if (gameInfo_.started() == s) + if (gameInfo_.started() == s) { return; + } gameInfo_.set_started(s); emit startedChanged(s); } void setSpectatorsOmniscient(bool v) { - if (gameInfo_.spectators_omniscient() == v) + if (gameInfo_.spectators_omniscient() == v) { return; + } gameInfo_.set_spectators_omniscient(v); emit spectatorsOmniscienceChanged(v); } diff --git a/cockatrice/src/game/game_state.h b/cockatrice/src/game/game_state.h index 54f6d9276..5a57d4321 100644 --- a/cockatrice/src/game/game_state.h +++ b/cockatrice/src/game/game_state.h @@ -1,8 +1,8 @@ /** * @file game_state.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_GAME_STATE_H #define COCKATRICE_GAME_STATE_H diff --git a/cockatrice/src/game/log/message_log_widget.h b/cockatrice/src/game/log/message_log_widget.h deleted file mode 100644 index 369debd33..000000000 --- a/cockatrice/src/game/log/message_log_widget.h +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @file message_log_widget.h - * @ingroup GameWidgets - * @brief TODO: Document this. - */ - -#ifndef MESSAGELOGWIDGET_H -#define MESSAGELOGWIDGET_H - -#include "../../interface/widgets/server/chat_view/chat_view.h" -#include "../zones/logic/card_zone_logic.h" - -class AbstractGame; -class CardItem; -class GameEventContext; -class Player; -class PlayerEventHandler; - -class MessageLogWidget : public ChatView -{ - Q_OBJECT -private: - enum MessageContext - { - MessageContext_None, - MessageContext_MoveCard, - MessageContext_Mulligan - }; - - MessageContext currentContext; - QString messagePrefix, messageSuffix; - - static QPair getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange); - -public: - void connectToPlayerEventHandler(PlayerEventHandler *player); - MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent = nullptr); - -public slots: - void containerProcessingDone(); - void containerProcessingStarted(const GameEventContext &context); - void logAlwaysRevealTopCard(Player *player, CardZoneLogic *zone, bool reveal); - void logAlwaysLookAtTopCard(Player *player, CardZoneLogic *zone, bool reveal); - void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); - void logConcede(int playerId); - void logUnconcede(int playerId); - void logConnectionStateChanged(Player *player, bool connectionState); - void logCreateArrow(Player *player, - Player *startPlayer, - QString startCard, - Player *targetPlayer, - QString targetCard, - bool playerTarget); - void logCreateToken(Player *player, QString cardName, QString pt, bool faceDown); - void logDeckSelect(Player *player, QString deckHash, int sideboardSize); - void logDestroyCard(Player *player, QString cardName); - void logDrawCards(Player *player, int number, bool deckIsEmpty); - void logDumpZone(Player *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); - void logFlipCard(Player *player, QString cardName, bool faceDown); - void logGameClosed(); - void logGameStart(); - void logGameFlooded(); - void logJoin(Player *player); - void logJoinSpectator(QString name); - void logKicked(); - void logLeave(Player *player, QString reason); - void logLeaveSpectator(QString name, QString reason); - void logNotReadyStart(Player *player); - void logMoveCard(Player *player, - CardItem *card, - CardZoneLogic *startZone, - int oldX, - CardZoneLogic *targetZone, - int newX); - void logMulligan(Player *player, int number); - void logReplayStarted(int gameId); - void logReadyStart(Player *player); - void logRevealCards(Player *player, - CardZoneLogic *zone, - int cardId, - QString cardName, - Player *otherPlayer, - bool faceDown, - int amount, - bool isLentToAnotherPlayer); - void logReverseTurn(Player *player, bool reversed); - void logRollDie(Player *player, int sides, const QList &rolls); - void logSay(Player *player, QString message); - void logSetActivePhase(int phase); - void logSetActivePlayer(Player *player); - void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); - void logSetCounter(Player *player, QString counterName, int value, int oldValue); - void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); - void logSetPT(Player *player, CardItem *card, QString newPT); - void logSetSideboardLock(Player *player, bool locked); - void logSetTapped(Player *player, CardItem *card, bool tapped); - void logShuffle(Player *player, CardZoneLogic *zone, int start, int end); - void logSpectatorSay(const ServerInfo_User &spectator, QString message); - void logUnattachCard(Player *player, QString cardName); - void logUndoDraw(Player *player, QString cardName); - void setContextJudgeName(QString player); - void appendHtmlServerMessage(const QString &html, - bool optionalIsBold = false, - QString optionalFontColor = QString()) override; -}; - -#endif diff --git a/cockatrice/src/game/phase.h b/cockatrice/src/game/phase.h index 2e712932f..a888ad43b 100644 --- a/cockatrice/src/game/phase.h +++ b/cockatrice/src/game/phase.h @@ -1,8 +1,8 @@ /** * @file phase.h * @ingroup GameLogic - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PHASE_H #define PHASE_H diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h index 3e743bdb3..4c7663789 100644 --- a/cockatrice/src/game/player/event_processing_options.h +++ b/cockatrice/src/game/player/event_processing_options.h @@ -1,8 +1,8 @@ /** * @file event_processing_options.h * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_EVENT_PROCESSING_OPTIONS_H #define COCKATRICE_EVENT_PROCESSING_OPTIONS_H diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp deleted file mode 100644 index 0dc381e28..000000000 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "player_menu.h" - -#include "../../../interface/widgets/tabs/tab_game.h" -#include "../../board/card_item.h" -#include "../../zones/hand_zone.h" -#include "../../zones/pile_zone.h" -#include "../../zones/table_zone.h" -#include "card_menu.h" -#include "hand_menu.h" - -#include - -PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player) -{ - playerMenu = new TearOffMenu(); - - if (player->getPlayerInfo()->getLocalOrJudge()) { - handMenu = addManagedMenu(player, player->getPlayerActions(), playerMenu); - libraryMenu = addManagedMenu(player, playerMenu); - } else { - handMenu = nullptr; - libraryMenu = nullptr; - } - - graveMenu = addManagedMenu(player, playerMenu); - rfgMenu = addManagedMenu(player, playerMenu); - - if (player->getPlayerInfo()->getLocalOrJudge()) { - sideboardMenu = addManagedMenu(player, playerMenu); - customZonesMenu = addManagedMenu(player); - playerMenu->addSeparator(); - - countersMenu = playerMenu->addMenu(QString()); - - utilityMenu = createManagedComponent(player, playerMenu); - } else { - sideboardMenu = nullptr; - customZonesMenu = nullptr; - countersMenu = nullptr; - utilityMenu = nullptr; - } - - if (player->getPlayerInfo()->getLocal()) { - sayMenu = addManagedMenu(player); - } else { - sayMenu = nullptr; - } - - connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, - &PlayerMenu::refreshShortcuts); - refreshShortcuts(); - - retranslateUi(); -} - -void PlayerMenu::setMenusForGraphicItems() -{ - player->getGraphicsItem()->getTableZoneGraphicsItem()->setMenu(playerMenu); - player->getGraphicsItem()->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard); - player->getGraphicsItem()->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg); - if (player->getPlayerInfo()->getLocalOrJudge()) { - player->getGraphicsItem()->getHandZoneGraphicsItem()->setMenu(handMenu); - player->getGraphicsItem()->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard); - player->getGraphicsItem()->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu); - } -} - -QMenu *PlayerMenu::updateCardMenu(const CardItem *card) -{ - if (!card) { - emit cardMenuUpdated(nullptr); - return nullptr; - } - - // If is spectator (as spectators don't need card menus), return - // only update the menu if the card is actually selected - if ((player->getGame()->getPlayerManager()->isSpectator() && !player->getGame()->getPlayerManager()->isJudge()) || - player->getGame()->getActiveCard() != card) { - return nullptr; - } - - QMenu *menu = new CardMenu(player, card, shortcutsActive); - emit cardMenuUpdated(menu); - - return menu; -} - -void PlayerMenu::retranslateUi() -{ - playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); - - for (auto *component : managedComponents) { - component->retranslateUi(); - } - - if (countersMenu) { - countersMenu->setTitle(tr("&Counters")); - } - - QMapIterator counterIterator(player->getCounters()); - while (counterIterator.hasNext()) { - counterIterator.next().value()->retranslateUi(); - } -} - -void PlayerMenu::refreshShortcuts() -{ - if (shortcutsActive) { - // Judges get access to every player's menus but only want shortcuts to be set for their own. - if (player->getPlayerInfo()->getLocalOrJudge() && !player->getPlayerInfo()->getLocal()) { - setShortcutsInactive(); - } else { - setShortcutsActive(); - } - } else { - setShortcutsInactive(); - } -} - -void PlayerMenu::setShortcutsActive() -{ - shortcutsActive = true; - - for (auto *component : managedComponents) { - component->setShortcutsActive(); - } - - // Counters implement AbstractPlayerComponent but are iterated via Player::counters - // (the authoritative source) rather than managedComponents to avoid a redundant - // list that must stay in sync with the map. - QMapIterator counterIterator(player->getCounters()); - while (counterIterator.hasNext()) { - counterIterator.next().value()->setShortcutsActive(); - } -} - -void PlayerMenu::setShortcutsInactive() -{ - shortcutsActive = false; - - for (auto *component : managedComponents) { - component->setShortcutsInactive(); - } - - QMapIterator counterIterator(player->getCounters()); - while (counterIterator.hasNext()) { - counterIterator.next().value()->setShortcutsInactive(); - } -} \ No newline at end of file diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 20034df16..de909ca5e 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -1,15 +1,13 @@ #include "player_actions.h" +#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h" +#include "../../game_graphics/dialogs/dlg_roll_dice.h" +#include "../../game_graphics/player/card_menu_action_type.h" +#include "../../game_graphics/zones/hand_zone.h" +#include "../../game_graphics/zones/table_zone.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/utility/get_text_with_max.h" -#include "../board/card_item.h" -#include "../client/settings/card_counter_settings.h" -#include "../dialogs/dlg_move_top_cards_until.h" -#include "../dialogs/dlg_roll_dice.h" -#include "../zones/hand_zone.h" -#include "../zones/logic/view_zone_logic.h" -#include "../zones/table_zone.h" -#include "card_menu_action_type.h" +#include "../zones/view_zone_logic.h" #include #include @@ -19,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -35,9 +34,11 @@ // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; -PlayerActions::PlayerActions(Player *_player) +PlayerActions::PlayerActions(PlayerLogic *_player) : QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false) { + connect(this, &PlayerActions::requestZoneViewToggle, player, &PlayerLogic::onRequestZoneViewToggle); + moveTopCardTimer = new QTimer(this); moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL); moveTopCardTimer->setSingleShot(true); @@ -84,8 +85,9 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) cardToMove->set_pt(info.getPowTough().toStdString()); } cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt); - if (tableRow != 3) + if (tableRow != 3) { cmd.set_target_zone(ZoneNames::TABLE); + } cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); } @@ -131,12 +133,12 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) void PlayerActions::actViewLibrary() { - player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, -1); + emit requestZoneViewToggle(ZoneNames::DECK, -1); } void PlayerActions::actViewHand() { - player->getGameScene()->toggleZoneView(player, ZoneNames::HAND, -1); + emit requestZoneViewToggle(ZoneNames::HAND, -1); } /** @@ -168,49 +170,45 @@ void PlayerActions::actSortHand() static QList defaultOptions = {CardList::SortByName, CardList::SortByPrinting}; - player->getGraphicsItem()->getHandZoneGraphicsItem()->sortHand(sortOptions + defaultOptions); + emit requestSortHand(sortOptions + defaultOptions); } -void PlayerActions::actViewTopCards() +void PlayerActions::actRequestViewTopCardsDialog() { - int deckSize = player->getDeckZone()->getCards().size(); - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("View top cards of library"), - tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1, - deckSize, 1, &ok); - if (ok) { - defaultNumberTopCards = number; - player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number); - } + emit requestViewTopCardsDialog(defaultNumberTopCards, player->getDeckZone()->getCards().size()); } -void PlayerActions::actViewBottomCards() +void PlayerActions::actViewTopCards(int number) { - int deckSize = player->getDeckZone()->getCards().size(); - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("View bottom cards of library"), - tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberBottomCards, 1, - deckSize, 1, &ok); - if (ok) { - defaultNumberBottomCards = number; - player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number, true); - } + defaultNumberTopCards = number; + emit requestZoneViewToggle(ZoneNames::DECK, number); } -void PlayerActions::actAlwaysRevealTopCard() +void PlayerActions::actRequestViewBottomCardsDialog() +{ + emit requestViewBottomCardsDialog(defaultNumberBottomCards, player->getDeckZone()->getCards().size()); +} + +void PlayerActions::actViewBottomCards(int number) +{ + defaultNumberBottomCards = number; + emit requestZoneViewToggle(ZoneNames::DECK, number, true); +} + +void PlayerActions::actAlwaysRevealTopCard(bool alwaysRevealTopCard) { Command_ChangeZoneProperties cmd; cmd.set_zone_name(ZoneNames::DECK); - cmd.set_always_reveal_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysRevealTopCardChecked()); + cmd.set_always_reveal_top_card(alwaysRevealTopCard); sendGameCommand(cmd); } -void PlayerActions::actAlwaysLookAtTopCard() +void PlayerActions::actAlwaysLookAtTopCard(bool alwaysRevealTopCard) { Command_ChangeZoneProperties cmd; cmd.set_zone_name(ZoneNames::DECK); - cmd.set_always_look_at_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysLookAtTopCardChecked()); + cmd.set_always_look_at_top_card(alwaysRevealTopCard); sendGameCommand(cmd); } @@ -222,17 +220,17 @@ void PlayerActions::actOpenDeckInDeckEditor() void PlayerActions::actViewGraveyard() { - player->getGameScene()->toggleZoneView(player, ZoneNames::GRAVE, -1); + emit requestZoneViewToggle(ZoneNames::GRAVE, -1); } void PlayerActions::actViewRfg() { - player->getGameScene()->toggleZoneView(player, ZoneNames::EXILE, -1); + emit requestZoneViewToggle(ZoneNames::EXILE, -1); } void PlayerActions::actViewSideboard() { - player->getGameScene()->toggleZoneView(player, ZoneNames::SIDEBOARD, -1); + emit requestZoneViewToggle(ZoneNames::SIDEBOARD, -1); } void PlayerActions::actShuffle() @@ -240,18 +238,20 @@ void PlayerActions::actShuffle() sendGameCommand(Command_Shuffle()); } -void PlayerActions::actShuffleTop() +void PlayerActions::actRequestShuffleTopDialog() { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { return; } - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Shuffle top cards of library"), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, - maxCards, 1, &ok); - if (!ok) { + emit requestShuffleTopDialog(defaultNumberTopCards, maxCards); +} + +void PlayerActions::actShuffleTop(int number) +{ + const int maxCards = player->getDeckZone()->getCards().size(); + if (maxCards == 0) { return; } @@ -269,18 +269,20 @@ void PlayerActions::actShuffleTop() sendGameCommand(cmd); } -void PlayerActions::actShuffleBottom() +void PlayerActions::actRequestShuffleBottomDialog() { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { return; } - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Shuffle bottom cards of library"), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, - maxCards, 1, &ok); - if (!ok) { + emit requestShuffleBottomDialog(defaultNumberBottomCards, maxCards); +} + +void PlayerActions::actShuffleBottom(int number) +{ + const int maxCards = player->getDeckZone()->getCards().size(); + if (maxCards == 0) { return; } @@ -305,21 +307,18 @@ void PlayerActions::actDrawCard() sendGameCommand(cmd); } -void PlayerActions::actMulligan() +void PlayerActions::actRequestMulliganDialog() { int startSize = SettingsCache::instance().getStartingHandSize(); int handSize = player->getHandZone()->getCards().size(); int deckSize = player->getDeckZone()->getCards().size() + handSize; - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"), - tr("Number of cards: (max. %1)").arg(deckSize) + '\n' + - tr("0 and lower are in comparison to current hand size"), - startSize, -handSize, deckSize, 1, &ok); + emit requestMulliganDialog(startSize, handSize, deckSize); +} - if (!ok) { - return; - } +void PlayerActions::actMulligan(int number) +{ + int handSize = player->getHandZone()->getCards().size(); if (number < 1) { number = handSize + number; @@ -353,19 +352,19 @@ void PlayerActions::doMulligan(int number) sendGameCommand(cmd); } -void PlayerActions::actDrawCards() +void PlayerActions::actRequestDrawCardsDialog() { int deckSize = player->getDeckZone()->getCards().size(); - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw cards"), - tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1, - deckSize, 1, &ok); - if (ok) { - defaultNumberTopCards = number; - Command_DrawCards cmd; - cmd.set_number(static_cast(number)); - sendGameCommand(cmd); - } + + emit requestDrawCardsDialog(defaultNumberTopCards, deckSize); +} + +void PlayerActions::actDrawCards(int number) +{ + defaultNumberTopCards = number; + Command_DrawCards cmd; + cmd.set_number(static_cast(number)); + sendGameCommand(cmd); } void PlayerActions::actUndoDraw() @@ -423,36 +422,40 @@ void PlayerActions::actMoveTopCardToExile() void PlayerActions::actMoveTopCardsToGrave() { - moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), false); + actRequestMoveTopCardsToDialog(ZoneNames::GRAVE, tr("grave"), false); } void PlayerActions::actMoveTopCardsToGraveFaceDown() { - moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), true); + actRequestMoveTopCardsToDialog(ZoneNames::GRAVE, tr("grave"), true); } void PlayerActions::actMoveTopCardsToExile() { - moveTopCardsTo(ZoneNames::EXILE, tr("exile"), false); + actRequestMoveTopCardsToDialog(ZoneNames::EXILE, tr("exile"), false); } void PlayerActions::actMoveTopCardsToExileFaceDown() { - moveTopCardsTo(ZoneNames::EXILE, tr("exile"), true); + actRequestMoveTopCardsToDialog(ZoneNames::EXILE, tr("exile"), true); } -void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) +void PlayerActions::actRequestMoveTopCardsToDialog(const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { return; } - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to %1").arg(zoneDisplayName), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, - maxCards, 1, &ok); - if (!ok) { + emit requestMoveTopCardsToDialog(defaultNumberTopCards, maxCards, targetZone, zoneDisplayName, faceDown); +} + +void PlayerActions::moveTopCardsTo(int number, const QString &targetZone, bool faceDown) +{ + const int maxCards = player->getDeckZone()->getCards().size(); + if (maxCards == 0) { return; } @@ -479,17 +482,16 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon sendGameCommand(cmd); } -void PlayerActions::actMoveTopCardsUntil() +void PlayerActions::actRequestMoveTopCardsUntilDialog() { stopMoveTopCardsUntil(); - DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions); - if (!dlg.exec()) { - return; - } + emit requestMoveTopCardsUntilDialog(movingCardsUntilOptions); +} - auto expr = dlg.getExpr(); - movingCardsUntilOptions = dlg.getOptions(); +void PlayerActions::moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOptions options) +{ + movingCardsUntilOptions = options; if (player->getDeckZone()->getCards().empty()) { stopMoveTopCardsUntil(); @@ -618,36 +620,40 @@ void PlayerActions::actMoveBottomCardToExile() void PlayerActions::actMoveBottomCardsToGrave() { - moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), false); + actRequestMoveBottomCardsToDialog(ZoneNames::GRAVE, tr("grave"), false); } void PlayerActions::actMoveBottomCardsToGraveFaceDown() { - moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), true); + actRequestMoveBottomCardsToDialog(ZoneNames::GRAVE, tr("grave"), true); } void PlayerActions::actMoveBottomCardsToExile() { - moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), false); + actRequestMoveBottomCardsToDialog(ZoneNames::EXILE, tr("exile"), false); } void PlayerActions::actMoveBottomCardsToExileFaceDown() { - moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), true); + actRequestMoveBottomCardsToDialog(ZoneNames::EXILE, tr("exile"), true); } -void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) +void PlayerActions::actRequestMoveBottomCardsToDialog(const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { return; } - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to %1").arg(zoneDisplayName), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, - maxCards, 1, &ok); - if (!ok) { + emit requestMoveBottomCardsToDialog(defaultNumberBottomCards, maxCards, targetZone, zoneDisplayName, faceDown); +} + +void PlayerActions::moveBottomCardsTo(int number, const QString &targetZone, bool faceDown) +{ + const int maxCards = player->getDeckZone()->getCards().size(); + if (maxCards == 0) { return; } @@ -759,20 +765,24 @@ void PlayerActions::actDrawBottomCard() sendGameCommand(cmd); } -void PlayerActions::actDrawBottomCards() +void PlayerActions::actRequestDrawBottomCardsDialog() { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { return; } - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw bottom cards"), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, - maxCards, 1, &ok); - if (!ok) { + emit requestDrawBottomCardsDialog(defaultNumberBottomCards, maxCards); +} + +void PlayerActions::actDrawBottomCards(int number) +{ + const int maxCards = player->getDeckZone()->getCards().size(); + if (maxCards == 0) { return; - } else if (number > maxCards) { + } + + if (number > maxCards) { number = maxCards; } defaultNumberBottomCards = number; @@ -839,27 +849,35 @@ void PlayerActions::actUntapAll() sendGameCommand(cmd); } -void PlayerActions::actRollDie() +void PlayerActions::actRequestRollDieDialog() { - DlgRollDice dlg(player->getGame()->getTab()); - if (!dlg.exec()) { - return; - } + emit requestRollDieDialog(); +} +void PlayerActions::actRollDie(int sides, int count) +{ Command_RollDie cmd; - cmd.set_sides(dlg.getDieSideCount()); - cmd.set_count(dlg.getDiceToRollCount()); + cmd.set_sides(sides); + cmd.set_count(count); sendGameCommand(cmd); } -void PlayerActions::actCreateToken() +void PlayerActions::actFlipCoin() { - DlgCreateToken dlg(player->getPlayerMenu()->getUtilityMenu()->getPredefinedTokens(), player->getGame()->getTab()); - if (!dlg.exec()) { - return; - } + Command_RollDie cmd; + cmd.set_sides(2); + cmd.set_count(1); + sendGameCommand(cmd); +} - lastTokenInfo = dlg.getTokenInfo(); +void PlayerActions::actRequestCreateTokenDialog(const QStringList &predefinedTokens) +{ + emit requestCreateTokenDialog(predefinedTokens); +} + +void PlayerActions::actCreateToken(TokenInfo tokenToCreate) +{ + lastTokenInfo = tokenToCreate; ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId}); if (correctedCard) { @@ -870,8 +888,7 @@ void PlayerActions::actCreateToken() } } - player->getPlayerMenu()->getUtilityMenu()->setAndEnableCreateAnotherTokenAction( - tr("C&reate another %1 token").arg(lastTokenInfo.name)); + emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name); actCreateAnotherToken(); } @@ -902,8 +919,12 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo) return; } - UtilityMenu *utilityMenu = player->getPlayerMenu()->getUtilityMenu(); - if (utilityMenu == nullptr || !utilityMenu->createAnotherTokenActionExists()) { + emit requestSetLastToken(cardInfo); +} + +void PlayerActions::setLastTokenInfo(CardInfoPtr cardInfo) +{ + if (cardInfo == nullptr) { return; } @@ -917,7 +938,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo) lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow); - utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name)); + emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name); } void PlayerActions::actCreatePredefinedToken() @@ -936,23 +957,17 @@ void PlayerActions::actCreatePredefinedToken() void PlayerActions::actCreateRelatedCard() { const CardItem *sourceCard = player->getGame()->getActiveCard(); + if (!sourceCard) { return; } + auto *action = static_cast(sender()); // If there is a better way of passing a CardRelation through a QAction, please add it here. auto relatedCards = sourceCard->getCardInfo().getAllRelatedCards(); - CardRelation *cardRelation = relatedCards.at(action->data().toInt()); - /* - * If we make a token via "Token: TokenName" - * then let's allow it to be created via "create another token" - */ - if (createRelatedFromRelation(sourceCard, cardRelation) && cardRelation->getCanCreateAnother()) { - ExactCard relatedCard = CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(), - sourceCard->getCard().getPrinting()); - setLastToken(relatedCard.getCardPtr()); - } + CardRelation *cardRelation = relatedCards.at(action->data().toInt()); + actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation); } void PlayerActions::actCreateAllRelatedCards() @@ -972,7 +987,9 @@ void PlayerActions::actCreateAllRelatedCards() if (relatedCards.length() == 1) { cardRelation = relatedCards.at(0); - if (createRelatedFromRelation(sourceCard, cardRelation)) { + lastRelatedCreationSucceeded = false; // reset before emit + actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation); + if (lastRelatedCreationSucceeded) { ++tokensTypesCreated; } } else { @@ -984,15 +1001,18 @@ void PlayerActions::actCreateAllRelatedCards() } } switch (nonExcludedRelatedCards.length()) { - case 1: // if nonExcludedRelatedCards == 1 + case 1: cardRelation = nonExcludedRelatedCards.at(0); - if (createRelatedFromRelation(sourceCard, cardRelation)) { + lastRelatedCreationSucceeded = false; // reset before emit + actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation); + if (lastRelatedCreationSucceeded) { ++tokensTypesCreated; } break; + // If all are marked "Exclude", then treat the situation as if none of them are. // We won't accept "garbage in, garbage out", here. - case 0: // else if nonExcludedRelatedCards == 0 + case 0: for (CardRelation *cardRelationAll : relatedCards) { if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) { dbName = cardRelationAll->getName(); @@ -1007,7 +1027,8 @@ void PlayerActions::actCreateAllRelatedCards() } } break; - default: // else + + default: for (CardRelation *cardRelationNotExcluded : nonExcludedRelatedCards) { if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) { dbName = cardRelationNotExcluded->getName(); @@ -1035,43 +1056,83 @@ void PlayerActions::actCreateAllRelatedCards() } } -bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation) +void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, + const CardRelation *cardRelation) +{ + emit requestCreateRelatedFromRelationDialog(sourceCard, cardRelation); +} + +bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, + const CardRelation *cardRelation, + int variableCount) { if (sourceCard == nullptr || cardRelation == nullptr) { return false; } - QString dbName = cardRelation->getName(); - bool persistent = cardRelation->getIsPersistent(); + + const QString dbName = cardRelation->getName(); + const bool persistent = cardRelation->getIsPersistent(); + + // Variable relations always use DoesNotAttach, regardless of the count the user + // entered. if (cardRelation->getIsVariable()) { - bool ok; - player->setDialogSemaphore(true); - int count = QInputDialog::getInt(player->getGame()->getTab(), tr("Create tokens"), tr("Number:"), - cardRelation->getDefaultCount(), 1, MAX_TOKENS_PER_DIALOG, 1, &ok); - player->setDialogSemaphore(false); - if (!ok) { + if (variableCount <= 0) { return false; } + for (int i = 0; i < variableCount; ++i) { + createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent); + } + return true; + } + + const int count = cardRelation->getDefaultCount(); + + if (count > 1) { for (int i = 0; i < count; ++i) { createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent); } - } else if (cardRelation->getDefaultCount() > 1) { - for (int i = 0; i < cardRelation->getDefaultCount(); ++i) { - createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent); - } - } else { - auto attachType = cardRelation->getAttachType(); - - // move card onto table first if attaching from some other zone - // we only do this for AttachTo because cross-zone TransformInto is already handled server-side - if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != ZoneNames::TABLE) { - playCardToTable(sourceCard, false); - } - - createCard(sourceCard, dbName, attachType, persistent); + return true; } + + CardRelationType attachType; + // do not attempt to attach to another player's cards, this causes the card to attempt to attach to the same + // cardid on the local player's field instead, which is an entirely different card! + if (player->getPlayerInfo()->getLocalOrJudge()) { + attachType = cardRelation->getAttachType(); + } else { + attachType = CardRelationType::DoesNotAttach; + } + + // move card onto table first if attaching from some other zone + // we only do this for AttachTo because cross-zone TransformInto is already handled server-side + if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != ZoneNames::TABLE) { + playCardToTable(sourceCard, false); + } + + createCard(sourceCard, dbName, attachType, persistent); return true; } +void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation) +{ + if (sourceCard == nullptr || cardRelation == nullptr) { + return; + } + + /* + * If we make a token via "Token: TokenName" + * then let's allow it to be created via "create another token" + */ + if (!cardRelation->getCanCreateAnother()) { + return; + } + + ExactCard relatedCard = + CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(), sourceCard->getCard().getPrinting()); + + setLastToken(relatedCard.getCardPtr()); +} + void PlayerActions::createCard(const CardItem *sourceCard, const QString &dbCardName, CardRelationType attachType, @@ -1149,95 +1210,29 @@ void PlayerActions::actSayMessage() sendGameCommand(cmd); } -void PlayerActions::setCardAttrHelper(const GameEventContext &context, - CardItem *card, - CardAttribute attribute, - const QString &avalue, - bool allCards, - EventProcessingOptions options) -{ - if (card == nullptr) { - return; - } - - bool moveCardContext = context.HasExtension(Context_MoveCard::ext); - switch (attribute) { - case AttrTapped: { - bool tapped = avalue == "1"; - if (!(!tapped && card->getDoesntUntap() && allCards)) { - if (!allCards) { - emit logSetTapped(player, card, tapped); - } - bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; - card->setTapped(tapped, canAnimate); - } - break; - } - case AttrAttacking: { - card->setAttacking(avalue == "1"); - break; - } - case AttrFaceDown: { - card->setFaceDown(avalue == "1"); - break; - } - case AttrColor: { - card->setColor(avalue); - break; - } - case AttrAnnotation: { - emit logSetAnnotation(player, card, avalue); - card->setAnnotation(avalue); - break; - } - case AttrDoesntUntap: { - bool value = (avalue == "1"); - emit logSetDoesntUntap(player, card, value); - card->setDoesntUntap(value); - break; - } - case AttrPT: { - emit logSetPT(player, card, avalue); - card->setPT(avalue); - break; - } - } -} - -void PlayerActions::actMoveCardXCardsFromTop() +void PlayerActions::actRequestMoveCardXCardsFromTopDialog() { int deckSize = player->getDeckZone()->getCards().size() + 1; // add the card to move to the deck - bool ok; - int number = - QInputDialog::getInt(player->getGame()->getTab(), tr("Place card X cards from top of library"), - tr("Which position should this card be placed:") + "\n" + tr("(max. %1)").arg(deckSize), - defaultNumberTopCardsToPlaceBelow, 1, deckSize, 1, &ok); - number -= 1; // indexes start at 0 - if (!ok) { - return; - } + emit requestMoveCardXCardsFromTopDialog(defaultNumberTopCardsToPlaceBelow, deckSize); +} +void PlayerActions::actMoveCardXCardsFromTop(QList selectedCards, int number) +{ defaultNumberTopCardsToPlaceBelow = number; - QList sel = player->getGameScene()->selectedItems(); - if (sel.isEmpty()) { + if (selectedCards.isEmpty()) { return; } - QList cardList; - while (!sel.isEmpty()) { - cardList.append(qgraphicsitem_cast(sel.takeFirst())); - } - QList commandList; ListOfCardsToMove idList; - for (const auto &i : cardList) { + for (const auto &i : selectedCards) { idList.add_card()->set_card_id(i->getId()); } - int startPlayerId = cardList[0]->getZone()->getPlayer()->getPlayerInfo()->getId(); - QString startZone = cardList[0]->getZone()->getName(); + int startPlayerId = selectedCards[0]->getZone()->getPlayer()->getPlayerInfo()->getId(); + QString startZone = selectedCards[0]->getZone()->getName(); auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); @@ -1256,15 +1251,14 @@ void PlayerActions::actMoveCardXCardsFromTop() } } -void PlayerActions::actIncPT(int deltaP, int deltaT) +void PlayerActions::actIncPT(QList selectedCards, int deltaP, int deltaT) { int playerid = player->getPlayerInfo()->getId(); QList commandList; - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); + for (auto card : selectedCards) { QString pt = card->getPT(); - const auto ptList = parsePT(pt); + const auto ptList = CardItem::parsePT(pt); QString newpt; if (ptList.isEmpty()) { newpt = QString::number(deltaP) + (deltaT ? "/" + QString::number(deltaT) : ""); @@ -1290,12 +1284,11 @@ void PlayerActions::actIncPT(int deltaP, int deltaT) player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } -void PlayerActions::actResetPT() +void PlayerActions::actResetPT(QList selectedCards) { int playerid = player->getPlayerInfo()->getId(); QList commandList; - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); + for (auto card : selectedCards) { QString ptString; if (!card->getFaceDown()) { // leave the pt empty if the card is face down ExactCard ec = card->getCard(); @@ -1324,68 +1317,32 @@ void PlayerActions::actResetPT() } } -QVariantList PlayerActions::parsePT(const QString &pt) -{ - QVariantList ptList = QVariantList(); - if (!pt.isEmpty()) { - int sep = pt.indexOf('/'); - if (sep == 0) { - ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string - } else { - int start = 0; - for (;;) { - QString item = pt.mid(start, sep - start); - if (item.isEmpty()) { - ptList.append(QVariant(QString())); - } else if (item[0] == '+') { - ptList.append(QVariant(item.mid(1).toInt())); // add as int - } else if (item[0] == '-') { - ptList.append(QVariant(item.toInt())); // add as int - } else { - ptList.append(QVariant(item)); // add as qstring - } - if (sep == -1) { - break; - } - start = sep + 1; - sep = pt.indexOf('/', start); - } - } - } - return ptList; -} - -void PlayerActions::actSetPT() +void PlayerActions::actRequestSetPTDialog(QList selectedCards) { QString oldPT; - int playerid = player->getPlayerInfo()->getId(); - auto sel = player->getGameScene()->selectedItems(); - for (const auto &item : sel) { - auto *card = static_cast(item); + for (auto card : selectedCards) { if (!card->getPT().isEmpty()) { oldPT = card->getPT(); } } - bool ok; - player->setDialogSemaphore(true); - QString pt = getTextWithMax(player->getGame()->getTab(), tr("Change power/toughness"), tr("Change stats to:"), - QLineEdit::Normal, oldPT, &ok); - player->setDialogSemaphore(false); - if (player->clearCardsToDelete() || !ok) { - return; - } - const auto ptList = parsePT(pt); + emit requestSetPTDialog(oldPT); +} + +void PlayerActions::actSetPT(QList selectedCards, const QString &pt) +{ + int playerid = player->getPlayerInfo()->getId(); + + const auto ptList = CardItem::parsePT(pt); bool empty = ptList.isEmpty(); QList commandList; - for (const auto &item : sel) { - auto *card = static_cast(item); + for (auto card : selectedCards) { auto *cmd = new Command_SetCardAttr; QString newpt = QString(); if (!empty) { - const auto oldpt = parsePT(card->getPT()); + const auto oldpt = CardItem::parsePT(card->getPT()); int ptIter = 0; for (const auto &_item : ptList) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -1425,44 +1382,69 @@ void PlayerActions::actDrawArrow() } } -void PlayerActions::actIncP() +void PlayerActions::actIncP(QList selectedCards) { - actIncPT(1, 0); + actIncPT(selectedCards, 1, 0); } -void PlayerActions::actDecP() +void PlayerActions::actDecP(QList selectedCards) { - actIncPT(-1, 0); + actIncPT(selectedCards, -1, 0); } -void PlayerActions::actIncT() +void PlayerActions::actIncT(QList selectedCards) { - actIncPT(0, 1); + actIncPT(selectedCards, 0, 1); } -void PlayerActions::actDecT() +void PlayerActions::actDecT(QList selectedCards) { - actIncPT(0, -1); + actIncPT(selectedCards, 0, -1); } -void PlayerActions::actIncPT() +void PlayerActions::actIncPT(QList selectedCards) { - actIncPT(1, 1); + actIncPT(selectedCards, 1, 1); } -void PlayerActions::actDecPT() +void PlayerActions::actDecPT(QList selectedCards) { - actIncPT(-1, -1); + actIncPT(selectedCards, -1, -1); } -void PlayerActions::actFlowP() +void PlayerActions::actFlowP(QList selectedCards) { - actIncPT(1, -1); + actIncPT(selectedCards, 1, -1); } -void PlayerActions::actFlowT() +void PlayerActions::actFlowT(QList selectedCards) { - actIncPT(-1, 1); + actIncPT(selectedCards, -1, 1); +} + +void PlayerActions::actReduceLifeByPower(QList selectedCards) +{ + // find life counter + auto lifeCounter = player->getLifeCounter(); + if (!lifeCounter) { + return; + } + + // calculate total power; + int total = 0; + for (auto card : selectedCards) { + QVariantList parsed = CardItem::parsePT(card->getPT()); + if (!parsed.isEmpty()) { + int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int + total += qMax(power, 0); + } + } + + // send cmd + Command_IncCounter cmd; + cmd.set_counter_id(lifeCounter->getId()); + cmd.set_delta(-total); + sendGameCommand(prepareGameCommand(cmd)); } void AnnotationDialog::keyPressEvent(QKeyEvent *event) @@ -1475,33 +1457,22 @@ void AnnotationDialog::keyPressEvent(QKeyEvent *event) QInputDialog::keyPressEvent(event); } -void PlayerActions::actSetAnnotation() +void PlayerActions::actRequestSetAnnotationDialog(QList selectedCards) { QString oldAnnotation; - auto sel = player->getGameScene()->selectedItems(); - for (const auto &item : sel) { - auto *card = static_cast(item); + for (auto card : selectedCards) { if (!card->getAnnotation().isEmpty()) { oldAnnotation = card->getAnnotation(); } } - player->setDialogSemaphore(true); - AnnotationDialog *dialog = new AnnotationDialog(player->getGame()->getTab()); - dialog->setOptions(QInputDialog::UsePlainTextEditForTextInput); - dialog->setWindowTitle(tr("Set annotation")); - dialog->setLabelText(tr("Please enter the new annotation:")); - dialog->setTextValue(oldAnnotation); - bool ok = dialog->exec(); - player->setDialogSemaphore(false); - if (player->clearCardsToDelete() || !ok) { - return; - } - QString annotation = dialog->textValue().left(MAX_NAME_LENGTH); + emit requestSetAnnotationDialog(oldAnnotation); +} +void PlayerActions::actSetAnnotation(QList selectedCards, const QString &annotation) +{ QList commandList; - for (const auto &item : sel) { - auto *card = static_cast(item); + for (auto card : selectedCards) { auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); @@ -1522,12 +1493,10 @@ void PlayerActions::actAttach() card->drawAttachArrow(); } -void PlayerActions::actUnattach() +void PlayerActions::actUnattach(QList selectedCards) { QList commandList; - for (QGraphicsItem *item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); - + for (auto card : selectedCards) { if (!card->getAttachedTo()) { continue; } @@ -1540,83 +1509,107 @@ void PlayerActions::actUnattach() sendGameCommand(prepareGameCommand(commandList)); } -void PlayerActions::actCardCounterTrigger() +void PlayerActions::actAddCardCounter(QList selectedCards, int counterId) +{ + offsetCardCounter(selectedCards, counterId, 1); +} + +void PlayerActions::actRemoveCardCounter(QList selectedCards, int counterId) +{ + offsetCardCounter(selectedCards, counterId, -1); +} + +void PlayerActions::offsetCardCounter(QList selectedCards, int counterId, int offset) { - auto *action = static_cast(sender()); - int counterId = action->data().toInt() / 1000; QList commandList; - switch (action->data().toInt() % 1000) { - case 9: { // increment counter - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); - if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) { - auto *cmd = new Command_SetCardCounter; - cmd->set_zone(card->getZone()->getName().toStdString()); - cmd->set_card_id(card->getId()); - cmd->set_counter_id(counterId); - cmd->set_counter_value(card->getCounters().value(counterId, 0) + 1); - commandList.append(cmd); - } - } - break; + for (auto card : selectedCards) { + int oldValue = card->getCounters().value(counterId, 0); + int newValue = oldValue + offset; + + // Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD]. + // Compare clamped value to allow recovery from invalid states. + int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD); + if (clampedValue != oldValue) { + auto *cmd = new Command_SetCardCounter; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(newValue); + commandList.append(cmd); } - case 10: { // decrement counter - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); - if (card->getCounters().value(counterId, 0)) { - auto *cmd = new Command_SetCardCounter; - cmd->set_zone(card->getZone()->getName().toStdString()); - cmd->set_card_id(card->getId()); - cmd->set_counter_id(counterId); - cmd->set_counter_value(card->getCounters().value(counterId, 0) - 1); - commandList.append(cmd); - } - } - break; - } - case 11: { // set counter with dialog - player->setDialogSemaphore(true); - - // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x" - QList sel = player->getGameScene()->selectedItems(); - QString oldValueForDlg = "x"; - if (sel.size() == 1) { - auto *card = dynamic_cast(sel.first()); - oldValueForDlg = QString::number(card->getCounters().value(counterId, 0)); - } - - auto &cardCounterSettings = SettingsCache::instance().cardCounters(); - QString counterName = cardCounterSettings.displayName(counterId); - - AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab()); - int ok = dialog.exec(); - - player->setDialogSemaphore(false); - if (player->clearCardsToDelete() || !ok) { - return; - } - - for (const auto &item : sel) { - auto *card = dynamic_cast(item); - - int oldValue = card->getCounters().value(counterId, 0); - Expression exp(oldValue); - int number = static_cast(exp.parse(dialog.textValue())); - - auto *cmd = new Command_SetCardCounter; - cmd->set_zone(card->getZone()->getName().toStdString()); - cmd->set_card_id(card->getId()); - cmd->set_counter_id(counterId); - cmd->set_counter_value(number); - commandList.append(cmd); - } - break; - } - default:; } + sendGameCommand(prepareGameCommand(commandList)); } +void PlayerActions::actRequestSetCardCounterDialog(QList selectedCards, int counterId) +{ + // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x" + QString oldValueForDlg = "x"; + if (selectedCards.size() == 1) { + auto *card = selectedCards.first(); + oldValueForDlg = QString::number(card->getCounters().value(counterId, 0)); + } + + emit requestSetCardCounterDialog(counterId, oldValueForDlg); +} + +void PlayerActions::actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue) +{ + QList commandList; + for (auto card : selectedCards) { + int oldValue = card->getCounters().value(counterId, 0); + Expression exp(oldValue); + double parsed = exp.parse(counterValue); + // Clamp in double precision first to avoid UB, then cast + int number = static_cast(qBound(0.0, parsed, static_cast(MAX_COUNTERS_ON_CARD))); + + auto *cmd = new Command_SetCardCounter; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(number); + commandList.append(cmd); + } + + sendGameCommand(prepareGameCommand(commandList)); +} + +void PlayerActions::actIncrementAllCardCounters(QList cardsToUpdate) +{ + if (cardsToUpdate.isEmpty()) { + // If no cards selected, update all cards on table + cardsToUpdate = static_cast>(player->getTableZone()->getCards()); + } + + QList commandList; + + for (const auto *card : cardsToUpdate) { + const auto &cardCounters = card->getCounters(); + + QMapIterator counterIterator(cardCounters); + while (counterIterator.hasNext()) { + counterIterator.next(); + int counterId = counterIterator.key(); + int currentValue = counterIterator.value(); + if (currentValue >= MAX_COUNTERS_ON_CARD) { + continue; + } + + auto cmd = std::make_unique(); + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(currentValue + 1); + commandList.append(cmd.release()); + } + } + + if (!commandList.isEmpty()) { + sendGameCommand(prepareGameCommand(commandList)); + } +} + /** * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone * is nullptr. @@ -1629,14 +1622,8 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone) return false; } -void PlayerActions::playSelectedCards(const bool faceDown) +void PlayerActions::playSelectedCards(QList selectedCards, const bool faceDown) { - QList selectedCards; - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); - selectedCards.append(card); - } - // CardIds will get shuffled downwards when cards leave the deck. // We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play // out the cards one-by-one. @@ -1650,19 +1637,19 @@ void PlayerActions::playSelectedCards(const bool faceDown) } } -void PlayerActions::actPlay() +void PlayerActions::actPlay(QList selectedCards) { - playSelectedCards(false); + playSelectedCards(selectedCards, false); } -void PlayerActions::actPlayFacedown() +void PlayerActions::actPlayFacedown(QList selectedCards) { - playSelectedCards(true); + playSelectedCards(selectedCards, true); } -void PlayerActions::actHide() +void PlayerActions::actHide(QList selectedCards) { - for (const auto &item : player->getGameScene()->selectedItems()) { + for (const auto &item : selectedCards) { auto *card = static_cast(item); if (card && isUnwritableRevealZone(card->getZone())) { card->getZone()->removeCard(card); @@ -1670,7 +1657,7 @@ void PlayerActions::actHide() } } -void PlayerActions::actReveal(QAction *action) +void PlayerActions::actReveal(QList selectedCards, QAction *action) { const int otherPlayerId = action->data().toInt(); @@ -1679,9 +1666,7 @@ void PlayerActions::actReveal(QAction *action) cmd.set_player_id(otherPlayerId); } - QList sel = player->getGameScene()->selectedItems(); - while (!sel.isEmpty()) { - const auto *card = qgraphicsitem_cast(sel.takeFirst()); + for (auto card : selectedCards) { if (!cmd.has_zone_name()) { cmd.set_zone_name(card->getZone()->getName().toStdString()); } @@ -1763,19 +1748,14 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId) sendGameCommand(cmd); } -void PlayerActions::cardMenuAction() +void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActionType type) { - auto *a = dynamic_cast(sender()); - QList sel = player->getGameScene()->selectedItems(); - QList cardList; - while (!sel.isEmpty()) { - cardList.append(qgraphicsitem_cast(sel.takeFirst())); - } + QList cardList = selectedCards; QList commandList; - if (a->data().toInt() <= (int)cmClone) { + if (type <= cmClone) { for (const auto &card : cardList) { - switch (static_cast(a->data().toInt())) { + switch (type) { // Leaving both for compatibility with server case cmUntap: // fallthrough @@ -1843,7 +1823,7 @@ void PlayerActions::cardMenuAction() return; } - Player *startPlayer = zone->getPlayer(); + PlayerLogic *startPlayer = zone->getPlayer(); if (!startPlayer) { return; } @@ -1856,7 +1836,7 @@ void PlayerActions::cardMenuAction() idList.add_card()->set_card_id(i->getId()); } - switch (static_cast(a->data().toInt())) { + switch (type) { case cmMoveToTopLibrary: { auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index d4c6daacf..3f1960892 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -2,21 +2,23 @@ * @file player_actions.h * @ingroup GameLogicActions * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H -#include "../dialogs/dlg_create_token.h" -#include "../dialogs/dlg_move_top_cards_until.h" + +#include "../../game_graphics/board/card_item.h" +#include "../../game_graphics/dialogs/dlg_create_token.h" +#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h" +#include "../../game_graphics/player/card_menu_action_type.h" #include "event_processing_options.h" -#include "player.h" +#include "player_logic.h" #include #include #include #include -#include namespace google { @@ -26,28 +28,21 @@ class Message; } } // namespace google -class CardItem; class Command_MoveCard; class GameEventContext; class PendingCommand; -class Player; +class PlayerLogic; class PlayerActions : public QObject { Q_OBJECT -signals: - void logSetTapped(Player *player, CardItem *card, bool tapped); - void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); - void logSetPT(Player *player, CardItem *card, QString newPT); - public: enum CardsToReveal { RANDOM_CARD_FROM_ZONE = -2 }; - explicit PlayerActions(Player *player); + explicit PlayerActions(PlayerLogic *player); void sendGameCommand(PendingCommand *pend); void sendGameCommand(const google::protobuf::Message &command); @@ -55,13 +50,6 @@ public: PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); PendingCommand *prepareGameCommand(const QList &cmdList); - void setCardAttrHelper(const GameEventContext &context, - CardItem *card, - CardAttribute attribute, - const QString &avalue, - bool allCards, - EventProcessingOptions options); - void moveOneCardUntil(CardItem *card); void stopMoveTopCardsUntil(); @@ -70,29 +58,75 @@ public: return movingCardsUntil; } +signals: + void requestViewTopCardsDialog(int defaultNumberTopCards, int deckSize); + void requestViewBottomCardsDialog(int defaultNumberBottomCards, int deckSize); + void requestShuffleTopDialog(int defaultNumberTopCards, int maxCards); + void requestShuffleBottomDialog(int defaultNumberBottomCards, int maxCards); + void requestMulliganDialog(int startSize, int handSize, int deckSize); + void requestDrawCardsDialog(int defaultNumberTopCards, int deckSize); + void requestMoveTopCardsToDialog(int defaultNumberTopCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown); + void requestMoveTopCardsUntilDialog(MoveTopCardsUntilOptions options); + void requestMoveBottomCardsToDialog(int defaultNumberBottomCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown); + void requestDrawBottomCardsDialog(int defaultNumberBottomCards, int maxCards); + void requestRollDieDialog(); + void requestCreateTokenDialog(const QStringList &predefinedTokens); + void requestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation); + void requestMoveCardXCardsFromTopDialog(int defaultNumberTopCardsToPlaceBelow, int deckSize); + void requestSetPTDialog(const QString &oldPT); + void requestSetAnnotationDialog(const QString &oldAnnotation); + void requestSetCardCounterDialog(int counterId, const QString &oldValueForDlg); + void requestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed = false); + void requestSortHand(const QList &options); + void requestEnableAndSetCreateAnotherTokenAction(const QString &lastTokenName); + void requestSetLastToken(CardInfoPtr lastToken); + public slots: void setLastToken(CardInfoPtr cardInfo); + void setLastTokenInfo(CardInfoPtr cardInfo); void playCard(CardItem *c, bool faceDown); void playCardToTable(const CardItem *c, bool faceDown); void actUntapAll(); - void actRollDie(); - void actCreateToken(); + void actRequestRollDieDialog(); + void actRollDie(int sides, int count); + void actFlipCoin(); + void actRequestCreateTokenDialog(const QStringList &predefinedTokens); + void actCreateToken(TokenInfo tokenToCreate); void actCreateAnotherToken(); + void actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation); + bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation, int variableCount); + void onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation); + void setLastRelatedCreationSucceeded(bool succeeded) + { + lastRelatedCreationSucceeded = succeeded; + } void actShuffle(); - void actShuffleTop(); - void actShuffleBottom(); + void actRequestShuffleTopDialog(); + void actShuffleTop(int number); + void actRequestShuffleBottomDialog(); + void actShuffleBottom(int number); void actDrawCard(); - void actDrawCards(); + void actRequestDrawCardsDialog(); + void actDrawCards(int number); void actUndoDraw(); - void actMulligan(); + void actRequestMulliganDialog(); + void actMulligan(int number); void actMulliganSameSize(); void actMulliganMinusOne(); void doMulligan(int number); - void actPlay(); - void actPlayFacedown(); - void actHide(); + void actPlay(QList selectedCards); + void actPlayFacedown(QList selectedCards); + void actHide(QList selectedCards); void actMoveTopCardToPlay(); void actMoveTopCardToPlayFaceDown(); @@ -102,10 +136,14 @@ public slots: void actMoveTopCardsToGraveFaceDown(); void actMoveTopCardsToExile(); void actMoveTopCardsToExileFaceDown(); - void actMoveTopCardsUntil(); + void actRequestMoveTopCardsUntilDialog(); + void moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOptions options); void actMoveTopCardToBottom(); + void actRequestMoveTopCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); + void moveTopCardsTo(int number, const QString &targetZone, bool faceDown); void actDrawBottomCard(); - void actDrawBottomCards(); + void actRequestDrawBottomCardsDialog(); + void actDrawBottomCards(int number); void actMoveBottomCardToPlay(); void actMoveBottomCardToPlayFaceDown(); void actMoveBottomCardToGrave(); @@ -115,6 +153,8 @@ public slots: void actMoveBottomCardsToExile(); void actMoveBottomCardsToExileFaceDown(); void actMoveBottomCardToTop(); + void actRequestMoveBottomCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); + void moveBottomCardsTo(int number, const QString &targetZone, bool faceDown); void actSelectAll(); void actSelectRow(); @@ -122,10 +162,12 @@ public slots: void actViewLibrary(); void actViewHand(); - void actViewTopCards(); - void actViewBottomCards(); - void actAlwaysRevealTopCard(); - void actAlwaysLookAtTopCard(); + void actRequestViewTopCardsDialog(); + void actViewTopCards(int number); + void actRequestViewBottomCardsDialog(); + void actViewBottomCards(int number); + void actAlwaysRevealTopCard(bool alwaysRevealTopCard); + void actAlwaysLookAtTopCard(bool alwaysRevealTopCard); void actViewGraveyard(); void actLendLibrary(int lendToPlayerId); void actRevealTopCards(int revealToPlayerId, int amount); @@ -140,34 +182,44 @@ public slots: void actCreateRelatedCard(); void actCreateAllRelatedCards(); - void actMoveCardXCardsFromTop(); - void actCardCounterTrigger(); + void actRequestMoveCardXCardsFromTopDialog(); + void actMoveCardXCardsFromTop(QList selectedCards, int number); + void actRemoveCardCounter(QList selectedCards, int counterId); + void actAddCardCounter(QList selectedCards, int counterId); + void actRequestSetCardCounterDialog(QList selectedCards, int counterId); + void actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue); + void actIncrementAllCardCounters(QList cardsToUpdate); void actAttach(); - void actUnattach(); + void actUnattach(QList selectedCards); void actDrawArrow(); - void actIncPT(int deltaP, int deltaT); - void actResetPT(); - void actSetPT(); - void actIncP(); - void actDecP(); - void actIncT(); - void actDecT(); - void actIncPT(); - void actDecPT(); - void actFlowP(); - void actFlowT(); - void actSetAnnotation(); - void actReveal(QAction *action); + void actIncPT(QList selectedCards, int deltaP, int deltaT); + void actResetPT(QList selectedCards); + void actRequestSetPTDialog(QList selectedCards); + void actSetPT(QList selectedCards, const QString &pt); + void actIncP(QList selectedCards); + void actDecP(QList selectedCards); + void actIncT(QList selectedCards); + void actDecT(QList selectedCards); + void actIncPT(QList selectedCards); + void actDecPT(QList selectedCards); + void actFlowP(QList selectedCards); + void actFlowT(QList selectedCards); + + void actReduceLifeByPower(QList selectedCards); + + void actRequestSetAnnotationDialog(QList selectedCards); + void actSetAnnotation(QList selectedCards, const QString &annotation); + void actReveal(QList selectedCards, QAction *action); void actRevealHand(int revealToPlayerId); void actRevealRandomHandCard(int revealToPlayerId); void actRevealLibrary(int revealToPlayerId); void actSortHand(); - void cardMenuAction(); + void cardMenuAction(QList selectedCards, CardMenuActionType type); private: - Player *player; + PlayerLogic *player; int defaultNumberTopCards = 1; int defaultNumberTopCardsToPlaceBelow = 1; @@ -183,21 +235,19 @@ private: int movingCardsUntilCounter = 0; MoveTopCardsUntilOptions movingCardsUntilOptions; - void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); - void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); + bool lastRelatedCreationSucceeded = false; void createCard(const CardItem *sourceCard, const QString &dbCardName, CardRelationType attach = CardRelationType::DoesNotAttach, bool persistent = false); - bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation); - void playSelectedCards(bool faceDown = false); + void playSelectedCards(QList selectedCards, bool faceDown = false); void cmdSetTopCard(Command_MoveCard &cmd); void cmdSetBottomCard(Command_MoveCard &cmd); - QVariantList parsePT(const QString &pt); + void offsetCardCounter(QList selectedCards, int counterId, int offset); }; #endif // COCKATRICE_PLAYER_ACTIONS_H diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 6fb1ff19a..bc48298f7 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -1,12 +1,13 @@ #include "player_event_handler.h" +#include "../../game_graphics/board/arrow_item.h" +#include "../../game_graphics/board/card_item.h" +#include "../../game_graphics/zones/view_zone.h" #include "../../interface/widgets/tabs/tab_game.h" -#include "../board/arrow_item.h" -#include "../board/card_item.h" +#include "../board/arrow_data.h" #include "../board/card_list.h" -#include "../zones/view_zone.h" -#include "player.h" #include "player_actions.h" +#include "player_logic.h" #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -30,10 +32,12 @@ #include #include #include +#include #include -PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player) +PlayerEventHandler::PlayerEventHandler(PlayerLogic *_player) : QObject(_player), player(_player) { + connect(this, &PlayerEventHandler::requestCardMenuUpdate, player, &PlayerLogic::requestCardMenuUpdate); } void PlayerEventHandler::eventGameSay(const Event_GameSay &event) @@ -89,25 +93,40 @@ void PlayerEventHandler::eventRollDie(const Event_RollDie &event) void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event) { - ArrowItem *arrow = player->addArrow(event.arrow_info()); - if (!arrow) { - return; + auto data = QSharedPointer::create(ArrowData::fromProto( + event.arrow_info(), player->getPlayerInfo()->getId(), player->getPlayerInfo()->getLocal())); + + const auto &playerList = player->getGame()->getPlayerManager()->getPlayers(); + PlayerLogic *startPlayer = playerList.value(data->startPlayerId); + PlayerLogic *targetPlayer = playerList.value(data->targetPlayerId); + + QString startCardName, targetCardName; + if (startPlayer) { + if (auto *zone = startPlayer->getZones().value(data->startZone)) { + if (auto *card = zone->getCard(data->startCardId)) { + startCardName = card->getName(); + } + } + } + if (!data->isPlayerTargeted() && targetPlayer) { + if (auto *zone = targetPlayer->getZones().value(data->targetZone)) { + if (auto *card = zone->getCard(data->targetCardId)) { + targetCardName = card->getName(); + } + } } - auto *startCard = static_cast(arrow->getStartItem()); - auto *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); - if (targetCard) { - emit logCreateArrow(player, startCard->getOwner(), startCard->getName(), targetCard->getOwner(), - targetCard->getName(), false); - } else { - emit logCreateArrow(player, startCard->getOwner(), startCard->getName(), arrow->getTargetItem()->getOwner(), - QString(), true); + emit player->arrowCreateRequested(data); + + if (startPlayer && targetPlayer && !startCardName.isEmpty() && + (data->isPlayerTargeted() || !targetCardName.isEmpty())) { + emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data->isPlayerTargeted()); } } void PlayerEventHandler::eventDeleteArrow(const Event_DeleteArrow &event) { - player->delArrow(event.arrow_id()); + emit player->arrowDeleted(player->getPlayerInfo()->getId(), event.arrow_id()); } void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event) @@ -149,8 +168,8 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event, if (!event.has_card_id()) { const CardList &cards = zone->getCards(); for (int i = 0; i < cards.size(); ++i) { - player->getPlayerActions()->setCardAttrHelper(context, cards.at(i), event.attribute(), - QString::fromStdString(event.attr_value()), true, options); + setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true, + options); } if (event.attribute() == AttrTapped) { emit logSetTapped(player, nullptr, event.attr_value() == "1"); @@ -161,8 +180,62 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event, qWarning() << "PlayerEventHandler::eventSetCardAttr: card id=" << event.card_id() << "not found"; return; } - player->getPlayerActions()->setCardAttrHelper(context, card, event.attribute(), - QString::fromStdString(event.attr_value()), false, options); + setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false, options); + } +} + +void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context, + CardItem *card, + CardAttribute attribute, + const QString &avalue, + bool allCards, + EventProcessingOptions options) +{ + if (card == nullptr) { + return; + } + + bool moveCardContext = context.HasExtension(Context_MoveCard::ext); + switch (attribute) { + case AttrTapped: { + bool tapped = avalue == "1"; + if (!(!tapped && card->getDoesntUntap() && allCards)) { + if (!allCards) { + emit logSetTapped(player, card, tapped); + } + bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; + card->setTapped(tapped, canAnimate); + } + break; + } + case AttrAttacking: { + card->setAttacking(avalue == "1"); + break; + } + case AttrFaceDown: { + card->setFaceDown(avalue == "1"); + break; + } + case AttrColor: { + card->setColor(avalue); + break; + } + case AttrAnnotation: { + emit logSetAnnotation(player, card, avalue); + card->setAnnotation(avalue); + break; + } + case AttrDoesntUntap: { + bool value = (avalue == "1"); + emit logSetDoesntUntap(player, card, value); + card->setDoesntUntap(value); + break; + } + case AttrPT: { + emit logSetPT(player, card, avalue); + card->setPT(avalue); + break; + } } } @@ -180,7 +253,7 @@ void PlayerEventHandler::eventSetCardCounter(const Event_SetCardCounter &event) int oldValue = card->getCounters().value(event.counter_id(), 0); card->setCounter(event.counter_id(), event.counter_value()); - player->getPlayerMenu()->updateCardMenu(card); + emit requestCardMenuUpdate(card); emit logSetCardCounter(player, card->getName(), event.counter_id(), event.counter_value(), oldValue); } @@ -191,7 +264,7 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event) void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event) { - AbstractCounter *ctr = player->getCounters().value(event.counter_id(), 0); + CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr); if (!ctr) { return; } @@ -207,7 +280,7 @@ void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event) void PlayerEventHandler::eventDumpZone(const Event_DumpZone &event) { - Player *zoneOwner = player->getGame()->getPlayerManager()->getPlayers().value(event.zone_owner_id(), 0); + PlayerLogic *zoneOwner = player->getGame()->getPlayerManager()->getPlayers().value(event.zone_owner_id(), 0); if (!zoneOwner) { return; } @@ -220,13 +293,13 @@ void PlayerEventHandler::eventDumpZone(const Event_DumpZone &event) void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context) { - Player *startPlayer = player->getGame()->getPlayerManager()->getPlayers().value(event.start_player_id()); + PlayerLogic *startPlayer = player->getGame()->getPlayerManager()->getPlayers().value(event.start_player_id()); if (!startPlayer) { return; } QString startZoneString = QString::fromStdString(event.start_zone()); CardZoneLogic *startZone = startPlayer->getZones().value(startZoneString, 0); - Player *targetPlayer = player->getGame()->getPlayerManager()->getPlayers().value(event.target_player_id()); + PlayerLogic *targetPlayer = player->getGame()->getPlayerManager()->getPlayers().value(event.target_player_id()); if (!targetPlayer) { return; } @@ -297,29 +370,8 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv targetZone->addCard(card, true, x, y); - // Look at all arrows from and to the card. - // If the card was moved to another zone, delete the arrows, otherwise update them. - QMapIterator playerIterator(player->getGame()->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) { - Player *p = playerIterator.next().value(); - - QList arrowsToDelete; - QMapIterator arrowIterator(p->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *arrow = arrowIterator.next().value(); - if ((arrow->getStartItem() == card) || (arrow->getTargetItem() == card)) { - if (startZone == targetZone) { - arrow->updatePath(); - } else { - arrowsToDelete.append(arrow); - } - } - } - for (auto &i : arrowsToDelete) { - i->delArrow(); - } - } - player->getPlayerMenu()->updateCardMenu(card); + emit cardZoneChanged(card, startZone == targetZone); + emit requestCardMenuUpdate(card); if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK && targetZone->getName() == ZoneNames::STACK) { @@ -346,7 +398,7 @@ void PlayerEventHandler::eventFlipCard(const Event_FlipCard &event) emit logFlipCard(player, card->getName(), event.face_down()); card->setFaceDown(event.face_down()); - player->getPlayerMenu()->updateCardMenu(card); + emit requestCardMenuUpdate(card); } void PlayerEventHandler::eventDestroyCard(const Event_DestroyCard &event) @@ -374,8 +426,8 @@ void PlayerEventHandler::eventDestroyCard(const Event_DestroyCard &event) void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event) { - const QMap &playerList = player->getGame()->getPlayerManager()->getPlayers(); - Player *targetPlayer = nullptr; + const QMap &playerList = player->getGame()->getPlayerManager()->getPlayers(); + PlayerLogic *targetPlayer = nullptr; CardZoneLogic *targetZone = nullptr; CardItem *targetCard = nullptr; if (event.has_target_player_id()) { @@ -415,7 +467,7 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event) } else { emit logUnattachCard(player, startCard->getName()); } - player->getPlayerMenu()->updateCardMenu(startCard); + emit requestCardMenuUpdate(startCard); } void PlayerEventHandler::eventDrawCards(const Event_DrawCards &event) @@ -452,7 +504,7 @@ void PlayerEventHandler::eventRevealCards(const Event_RevealCards &event, EventP if (!zone) { return; } - Player *otherPlayer = nullptr; + PlayerLogic *otherPlayer = nullptr; if (event.has_other_player_id()) { otherPlayer = player->getGame()->getPlayerManager()->getPlayers().value(event.other_player_id()); if (!otherPlayer) { @@ -501,7 +553,7 @@ void PlayerEventHandler::eventRevealCards(const Event_RevealCards &event, EventP } if (!options.testFlag(SKIP_REVEAL_WINDOW) && showZoneView && !cardList.isEmpty()) { - player->getGameScene()->addRevealedZoneView(player, zone, cardList, event.grant_write_access()); + emit player->requestRevealedZoneView(player, zone, cardList, event.grant_write_access()); } emit logRevealCards(player, zone, cardId, cardName, otherPlayer, false, @@ -527,6 +579,18 @@ void PlayerEventHandler::eventChangeZoneProperties(const Event_ChangeZonePropert } } +void PlayerEventHandler::eventGameLogNotice(const Event_GameLogNotice &event) +{ + Event_GameLogNotice::NoticeType type = event.notice_type(); + switch (type) { + case Event_GameLogNotice::UNDO_DRAW_FAILED: + emit logUndoDrawFailed(player); + break; + default: + qWarning() << "Received Event_GameLogNotice with unknown noticeType: " << type; + } +} + void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type, const GameEvent &event, const GameEventContext &context, @@ -590,6 +654,9 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type, case GameEvent::CHANGE_ZONE_PROPERTIES: eventChangeZoneProperties(event.GetExtension(Event_ChangeZoneProperties::ext)); break; + case GameEvent::GAME_LOG_NOTICE: + eventGameLogNotice(event.GetExtension(Event_GameLogNotice::ext)); + break; default: { qWarning() << "unhandled game event" << type; } diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 5d1ddd4b4..cfd82933f 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -1,20 +1,21 @@ /** * @file player_event_handler.h * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_EVENT_HANDLER_H #define COCKATRICE_PLAYER_EVENT_HANDLER_H #include "event_processing_options.h" #include +#include #include #include class CardItem; class CardZoneLogic; -class Player; +class PlayerLogic; class Event_AttachCard; class Event_ChangeZoneProperties; class Event_CreateArrow; @@ -34,53 +35,58 @@ class Event_SetCardAttr; class Event_SetCardCounter; class Event_SetCounter; class Event_Shuffle; +class Event_GameLogNotice; + class PlayerEventHandler : public QObject { Q_OBJECT signals: - void logSay(Player *player, QString message); - void logShuffle(Player *player, CardZoneLogic *zone, int start, int end); - void logRollDie(Player *player, int sides, const QList &rolls); - void logCreateArrow(Player *player, - Player *startPlayer, + void logSay(PlayerLogic *player, QString message); + void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end); + void logRollDie(PlayerLogic *player, int sides, const QList &rolls); + void logCreateArrow(PlayerLogic *player, + PlayerLogic *startPlayer, QString startCard, - Player *targetPlayer, + PlayerLogic *targetPlayer, QString targetCard, bool _playerTarget); - void logCreateToken(Player *player, QString cardName, QString pt, bool faceDown); - void logDrawCards(Player *player, int number, bool deckIsEmpty); - void logUndoDraw(Player *player, QString cardName); - void logMoveCard(Player *player, + void logCreateToken(PlayerLogic *player, QString cardName, QString pt, bool faceDown); + void logDrawCards(PlayerLogic *player, int number, bool deckIsEmpty); + void logUndoDraw(PlayerLogic *player, QString cardName); + void logUndoDrawFailed(PlayerLogic *player); + void logMoveCard(PlayerLogic *player, CardItem *card, CardZoneLogic *startZone, int oldX, CardZoneLogic *targetZone, int newX); - void logFlipCard(Player *player, QString cardName, bool faceDown); - void logDestroyCard(Player *player, QString cardName); - void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); - void logUnattachCard(Player *player, QString cardName); - void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); - void logSetTapped(Player *player, CardItem *card, bool tapped); - void logSetCounter(Player *player, QString counterName, int value, int oldValue); - void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); - void logSetPT(Player *player, CardItem *card, QString newPT); - void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logDumpZone(Player *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); - void logRevealCards(Player *player, + void logFlipCard(PlayerLogic *player, QString cardName, bool faceDown); + void logDestroyCard(PlayerLogic *player, QString cardName); + void logAttachCard(PlayerLogic *player, QString cardName, PlayerLogic *targetPlayer, QString targetCardName); + void logUnattachCard(PlayerLogic *player, QString cardName); + void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue); + void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped); + void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue); + void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap); + void logSetPT(PlayerLogic *player, CardItem *card, QString newPT); + void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation); + void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); + void logRevealCards(PlayerLogic *player, CardZoneLogic *zone, int cardId, QString cardName, - Player *otherPlayer, + PlayerLogic *otherPlayer, bool faceDown, int amount, bool isLentToAnotherPlayer = false); - void logAlwaysRevealTopCard(Player *player, CardZoneLogic *zone, bool reveal); - void logAlwaysLookAtTopCard(Player *player, CardZoneLogic *zone, bool reveal); + void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + void cardZoneChanged(CardItem *card, bool sameZone); + void requestCardMenuUpdate(const CardItem *card); public: - PlayerEventHandler(Player *player); + PlayerEventHandler(PlayerLogic *player); void processGameEvent(GameEvent::GameEventType type, const GameEvent &event, @@ -107,9 +113,17 @@ public: void eventDrawCards(const Event_DrawCards &event); void eventRevealCards(const Event_RevealCards &event, EventProcessingOptions options); void eventChangeZoneProperties(const Event_ChangeZoneProperties &event); + void eventGameLogNotice(const Event_GameLogNotice &event); private: - Player *player; + PlayerLogic *player; + + void setCardAttrHelper(const GameEventContext &context, + CardItem *card, + CardAttribute attribute, + const QString &avalue, + bool allCards, + EventProcessingOptions options); }; #endif // COCKATRICE_PLAYER_EVENT_HANDLER_H diff --git a/cockatrice/src/game/player/player_info.cpp b/cockatrice/src/game/player/player_info.cpp index 8507f75eb..c4debde0f 100644 --- a/cockatrice/src/game/player/player_info.cpp +++ b/cockatrice/src/game/player/player_info.cpp @@ -1,7 +1,7 @@ #include "player_info.h" PlayerInfo::PlayerInfo(const ServerInfo_User &info, int _id, bool _local, bool _judge) - : id(_id), local(_local), judge(_judge), handVisible(false) + : id(_id), local(_local), judge(_judge) { userInfo = new ServerInfo_User; userInfo->CopyFrom(info); diff --git a/cockatrice/src/game/player/player_info.h b/cockatrice/src/game/player/player_info.h index 2e9b49d6d..4ec39edbd 100644 --- a/cockatrice/src/game/player/player_info.h +++ b/cockatrice/src/game/player/player_info.h @@ -1,13 +1,13 @@ /** * @file player_info.h * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_INFO_H #define COCKATRICE_PLAYER_INFO_H -#include "player_target.h" +#include "../../game_graphics/player/player_target.h" #include #include @@ -22,7 +22,6 @@ public: int id; bool local; bool judge; - bool handVisible; int getId() const { @@ -51,16 +50,6 @@ public: return judge; } - void setHandVisible(bool _handVisible) - { - handVisible = _handVisible; - } - - bool getHandVisible() const - { - return handVisible; - } - QString getName() const { return QString::fromStdString(userInfo->name()); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player_logic.cpp similarity index 53% rename from cockatrice/src/game/player/player.cpp rename to cockatrice/src/game/player/player_logic.cpp index ac4149f0e..485e2fc5c 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player_logic.cpp @@ -1,18 +1,18 @@ -#include "player.h" +#include "player_logic.h" +#include "../../game_graphics/board/arrow_item.h" +#include "../../game_graphics/board/card_item.h" +#include "../../game_graphics/board/counter_general.h" +#include "../../game_graphics/game_scene.h" +#include "../../game_graphics/player/player_target.h" +#include "../../game_graphics/zones/hand_zone.h" +#include "../../game_graphics/zones/pile_zone.h" +#include "../../game_graphics/zones/stack_zone.h" +#include "../../game_graphics/zones/table_zone.h" #include "../../interface/theme_manager.h" #include "../../interface/widgets/tabs/tab_game.h" -#include "../board/arrow_item.h" -#include "../board/card_item.h" #include "../board/card_list.h" -#include "../board/counter_general.h" -#include "../game_scene.h" -#include "../zones/hand_zone.h" -#include "../zones/pile_zone.h" -#include "../zones/stack_zone.h" -#include "../zones/table_zone.h" #include "player_actions.h" -#include "player_target.h" #include #include @@ -29,37 +29,15 @@ #include #include -Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent) +PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent) : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false), conceded(false), zoneId(0), dialogSemaphore(false) { initializeZones(); - - playerMenu = new PlayerMenu(this); - graphicsItem = new PlayerGraphicsItem(this); - playerMenu->setMenusForGraphicItems(); - - connect(this, &Player::activeChanged, graphicsItem, &PlayerGraphicsItem::onPlayerActiveChanged); - - connect(this, &Player::openDeckEditor, game->getTab(), &TabGame::openDeckEditor); - - forwardActionSignalsToEventHandler(); } -// Event Handler is the controller i.e. everything hooks up to this to know about player state -// Player should forward (private) signals to the event handler - -void Player::forwardActionSignalsToEventHandler() -{ - connect(playerActions, &PlayerActions::logSetTapped, playerEventHandler, &PlayerEventHandler::logSetTapped); - connect(playerActions, &PlayerActions::logSetDoesntUntap, playerEventHandler, - &PlayerEventHandler::logSetDoesntUntap); - connect(playerActions, &PlayerActions::logSetAnnotation, playerEventHandler, &PlayerEventHandler::logSetAnnotation); - connect(playerActions, &PlayerActions::logSetPT, playerEventHandler, &PlayerEventHandler::logSetPT); -} - -void Player::initializeZones() +void PlayerLogic::initializeZones() { addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this)); addZone(new PileZoneLogic(this, ZoneNames::GRAVE, false, false, true, this)); @@ -72,22 +50,22 @@ void Player::initializeZones() addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this)); } -Player::~Player() +PlayerLogic::~PlayerLogic() { qCInfo(PlayerLog) << "Player destructor:" << getPlayerInfo()->getName(); QMapIterator i(zones); - while (i.hasNext()) + while (i.hasNext()) { delete i.next().value(); + } zones.clear(); - delete playerMenu; delete getPlayerInfo()->userInfo; } -void Player::clear() +void PlayerLogic::clear() { - clearArrows(); + emit arrowsClearedLocally(); QMapIterator i(zones); while (i.hasNext()) { @@ -97,12 +75,11 @@ void Player::clear() clearCounters(); } -void Player::setConceded(bool _conceded) +void PlayerLogic::setConceded(bool _conceded) { if (conceded != _conceded) { conceded = _conceded; - getGraphicsItem()->setVisible(!conceded); if (conceded) { clear(); } @@ -110,13 +87,15 @@ void Player::setConceded(bool _conceded) } } -void Player::setZoneId(int _zoneId) +void PlayerLogic::setZoneId(int _zoneId) { - zoneId = _zoneId; - graphicsItem->getPlayerArea()->setPlayerZoneId(zoneId); + if (zoneId != _zoneId) { + zoneId = _zoneId; + emit zoneIdChanged(zoneId); + } } -void Player::processPlayerInfo(const ServerInfo_Player &info) +void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info) { static QSet builtinZones{/* PileZones */ ZoneNames::DECK, ZoneNames::GRAVE, ZoneNames::EXILE, ZoneNames::SIDEBOARD, @@ -127,7 +106,7 @@ void Player::processPlayerInfo(const ServerInfo_Player &info) /* HandZone */ ZoneNames::HAND}; clearCounters(); - clearArrows(); + emit arrowsClearedLocally(); QMutableMapIterator zoneIt(zones); while (zoneIt.hasNext()) { @@ -214,7 +193,7 @@ void Player::processPlayerInfo(const ServerInfo_Player &info) setConceded(info.properties().conceded()); } -void Player::processCardAttachment(const ServerInfo_Player &info) +void PlayerLogic::processCardAttachment(const ServerInfo_Player &info) { const int zoneListSize = info.zone_list_size(); for (int i = 0; i < zoneListSize; ++i) { @@ -243,16 +222,17 @@ void Player::processCardAttachment(const ServerInfo_Player &info) const int arrowListSize = info.arrow_list_size(); for (int i = 0; i < arrowListSize; ++i) { - addArrow(info.arrow_list(i)); + emit arrowCreateRequested(QSharedPointer::create( + ArrowData::fromProto(info.arrow_list(i), getPlayerInfo()->getId(), getPlayerInfo()->getLocal()))); } } -void Player::addCard(CardItem *card) +void PlayerLogic::addCard(CardItem *card) { emit newCardAdded(card); } -void Player::deleteCard(CardItem *card) +void PlayerLogic::deleteCard(CardItem *card) { if (card == nullptr) { return; @@ -263,179 +243,60 @@ void Player::deleteCard(CardItem *card) } } -void Player::setDeck(const DeckList &_deck) +void PlayerLogic::setDeck(const DeckList &_deck) { deck = _deck; emit deckChanged(); } -AbstractCounter *Player::addCounter(const ServerInfo_Counter &counter) +CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter) { return addCounter(counter.id(), QString::fromStdString(counter.name()), convertColorToQColor(counter.counter_color()), counter.radius(), counter.count()); } -AbstractCounter *Player::addCounter(int counterId, const QString &name, QColor color, int radius, int value) +CounterState *PlayerLogic::addCounter(int id, const QString &name, const QColor &color, int radius, int value) { - if (counters.contains(counterId)) { + if (counters.contains(id)) { return nullptr; } - - AbstractCounter *ctr; - if (name == "life") { - ctr = getGraphicsItem()->getPlayerTarget()->addCounter(counterId, name, value); - } else { - ctr = new GeneralCounter(this, counterId, name, color, radius, value, true, graphicsItem); - } - counters.insert(counterId, ctr); - - if (playerMenu->getCountersMenu() && ctr->getMenu()) { - playerMenu->getCountersMenu()->addMenu(ctr->getMenu()); - } - if (playerMenu->getShortcutsActive()) { - ctr->setShortcutsActive(); - } - emit rearrangeCounters(); - return ctr; + auto *state = new CounterState(id, name, color, radius, value, this); + counters.insert(id, state); + emit counterAdded(state); + return state; } -void Player::delCounter(int counterId) +void PlayerLogic::delCounter(int id) { - AbstractCounter *ctr = counters.value(counterId, 0); - if (!ctr) { + auto *state = counters.take(id); + if (!state) { return; } - - ctr->delCounter(); - counters.remove(counterId); - emit rearrangeCounters(); + emit counterRemoved(id); + state->deleteLater(); } -void Player::clearCounters() +void PlayerLogic::clearCounters() { - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) { - counterIterator.next().value()->delCounter(); + for (int id : counters.keys()) { + emit counterRemoved(id); } + qDeleteAll(counters); counters.clear(); } -void Player::incrementAllCardCounters() +CounterState *PlayerLogic::getLifeCounter() const { - QList cardsToUpdate; - - auto selectedItems = getGameScene()->selectedItems(); - if (!selectedItems.isEmpty()) { - // If cards are selected, only update those - for (const auto &item : selectedItems) { - auto *card = static_cast(item); - cardsToUpdate.append(card); - } - } else { - // If no cards selected, update all cards on table - const CardList &tableCards = getTableZone()->getCards(); - cardsToUpdate = tableCards; - } - - QList commandList; - - for (const auto *card : cardsToUpdate) { - const auto &cardCounters = card->getCounters(); - - QMapIterator counterIterator(cardCounters); - while (counterIterator.hasNext()) { - counterIterator.next(); - int counterId = counterIterator.key(); - int currentValue = counterIterator.value(); - if (currentValue >= MAX_COUNTERS_ON_CARD) { - continue; - } - - auto cmd = std::make_unique(); - cmd->set_zone(card->getZone()->getName().toStdString()); - cmd->set_card_id(card->getId()); - cmd->set_counter_id(counterId); - cmd->set_counter_value(currentValue + 1); - commandList.append(cmd.release()); + for (auto *s : counters.values()) { + if (s->getName() == "life") { + return s; } } - - if (!commandList.isEmpty()) { - playerActions->sendGameCommand(playerActions->prepareGameCommand(commandList)); - } + return nullptr; } -ArrowItem *Player::addArrow(const ServerInfo_Arrow &arrow) -{ - const QMap &playerList = game->getPlayerManager()->getPlayers(); - Player *startPlayer = playerList.value(arrow.start_player_id(), 0); - Player *targetPlayer = playerList.value(arrow.target_player_id(), 0); - if (!startPlayer || !targetPlayer) { - return nullptr; - } - - CardZoneLogic *startZone = startPlayer->getZones().value(QString::fromStdString(arrow.start_zone()), 0); - CardZoneLogic *targetZone = nullptr; - if (arrow.has_target_zone()) { - targetZone = targetPlayer->getZones().value(QString::fromStdString(arrow.target_zone()), 0); - } - if (!startZone || (!targetZone && arrow.has_target_zone())) { - return nullptr; - } - - CardItem *startCard = startZone->getCard(arrow.start_card_id()); - CardItem *targetCard = nullptr; - if (targetZone) { - targetCard = targetZone->getCard(arrow.target_card_id()); - } - if (!startCard || (!targetCard && arrow.has_target_card_id())) { - return nullptr; - } - - if (targetCard) { - return addArrow(arrow.id(), startCard, targetCard, convertColorToQColor(arrow.arrow_color())); - } else { - return addArrow(arrow.id(), startCard, targetPlayer->getGraphicsItem()->getPlayerTarget(), - convertColorToQColor(arrow.arrow_color())); - } -} - -ArrowItem *Player::addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color) -{ - auto *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); - arrows.insert(arrowId, arrow); - - getGameScene()->addItem(arrow); - return arrow; -} - -void Player::delArrow(int arrowId) -{ - ArrowItem *arr = arrows.value(arrowId, 0); - if (!arr) { - return; - } - arr->delArrow(); -} - -void Player::removeArrow(ArrowItem *arrow) -{ - if (arrow->getId() != -1) { - arrows.remove(arrow->getId()); - } -} - -void Player::clearArrows() -{ - QMapIterator arrowIterator(arrows); - while (arrowIterator.hasNext()) { - arrowIterator.next().value()->delArrow(); - } - arrows.clear(); -} - -bool Player::clearCardsToDelete() +bool PlayerLogic::clearCardsToDelete() { if (cardsToDelete.isEmpty()) { return false; @@ -451,28 +312,22 @@ bool Player::clearCardsToDelete() return true; } -void Player::setActive(bool _active) +void PlayerLogic::setActive(bool _active) { active = _active; emit activeChanged(active); } +void PlayerLogic::onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed) +{ + emit requestZoneViewToggle(this, zoneName, numberCards, isReversed); +} -void Player::updateZones() +void PlayerLogic::updateZones() { getTableZone()->reorganizeCards(); } -PlayerGraphicsItem *Player::getGraphicsItem() -{ - return graphicsItem; -} - -GameScene *Player::getGameScene() -{ - return getGraphicsItem()->getGameScene(); -} - -void Player::setGameStarted() +void PlayerLogic::setGameStarted() { if (playerInfo->local) { emit resetTopCardMenuActions(); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player_logic.h similarity index 72% rename from cockatrice/src/game/player/player.h rename to cockatrice/src/game/player/player_logic.h index e9c008821..a89cb6eed 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player_logic.h @@ -1,23 +1,21 @@ /** * @file player.h * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PLAYER_H #define PLAYER_H -#include "../../game_graphics/board/abstract_graphics_item.h" +#include "../../game_graphics/player/player_area.h" #include "../../interface/widgets/menus/tearoff_menu.h" +#include "../board/arrow_data.h" #include "../interface/deck_loader/loaded_deck.h" -#include "../zones/logic/hand_zone_logic.h" -#include "../zones/logic/pile_zone_logic.h" -#include "../zones/logic/stack_zone_logic.h" -#include "../zones/logic/table_zone_logic.h" -#include "menu/player_menu.h" -#include "player_area.h" +#include "../zones/hand_zone_logic.h" +#include "../zones/pile_zone_logic.h" +#include "../zones/stack_zone_logic.h" +#include "../zones/table_zone_logic.h" #include "player_event_handler.h" -#include "player_graphics_item.h" #include "player_info.h" #include @@ -39,7 +37,6 @@ class Message; } } // namespace google class AbstractCardItem; -class AbstractCounter; class AbstractGame; class ArrowItem; class ArrowTarget; @@ -55,6 +52,7 @@ class PlayerMenu; class QAction; class QMenu; class ServerInfo_Arrow; +class ServerInfo_Card; class ServerInfo_Counter; class ServerInfo_Player; class ServerInfo_User; @@ -62,28 +60,41 @@ class TabGame; const int MAX_TOKENS_PER_DIALOG = 99; -class Player : public QObject +class PlayerLogic : public QObject { Q_OBJECT signals: void openDeckEditor(const LoadedDeck &deck); + void requestZoneViewToggle(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed); + void requestRevealedZoneView(PlayerLogic *player, + CardZoneLogic *zone, + const QList &cardList, + bool withWritePermission); void deckChanged(); void newCardAdded(AbstractCardItem *card); + void requestCardMenuUpdate(const CardItem *card); + void counterAdded(CounterState *state); + void counterRemoved(int counterId); void rearrangeCounters(); void activeChanged(bool active); + void zoneIdChanged(int zoneId); void concededChanged(int playerId, bool conceded); void clearCustomZonesMenu(); void addViewCustomZoneActionToCustomZoneMenu(QString zoneName); void resetTopCardMenuActions(); + void arrowCreateRequested(QSharedPointer data); + void arrowDeleteRequested(int creatorId, int arrowId); + void arrowDeleted(int creatorId, int arrowId); + void arrowsClearedLocally(); // fires on clear() and processPlayerInfo public slots: void setActive(bool _active); + void onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed); public: - Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); - void forwardActionSignalsToEventHandler(); - ~Player() override; + PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); + ~PlayerLogic() override; void initializeZones(); void updateZones(); @@ -107,10 +118,6 @@ public: return game; } - GameScene *getGameScene(); - - [[nodiscard]] PlayerGraphicsItem *getGraphicsItem(); - [[nodiscard]] PlayerActions *getPlayerActions() const { return playerActions; @@ -126,11 +133,6 @@ public: return playerInfo; } - [[nodiscard]] PlayerMenu *getPlayerMenu() const - { - return playerMenu; - } - void setDeck(const DeckList &_deck); [[nodiscard]] const DeckList &getDeck() const @@ -189,27 +191,20 @@ public: return qobject_cast(zones.value(ZoneNames::HAND)); } - AbstractCounter *addCounter(const ServerInfo_Counter &counter); - AbstractCounter *addCounter(int counterId, const QString &name, QColor color, int radius, int value); + CounterState *addCounter(const ServerInfo_Counter &counter); + CounterState *addCounter(int id, const QString &name, const QColor &color, int radius, int value); void delCounter(int counterId); void clearCounters(); - void incrementAllCardCounters(); - QMap getCounters() + QMap getCounters() const { return counters; } - ArrowItem *addArrow(const ServerInfo_Arrow &arrow); - ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color); - void delArrow(int arrowId); - void removeArrow(ArrowItem *arrow); - void clearArrows(); - - const QMap &getArrows() const - { - return arrows; - } + /** + * Gets the counter that represents the life total. + */ + CounterState *getLifeCounter() const; void setConceded(bool _conceded); bool getConceded() const @@ -236,8 +231,6 @@ private: PlayerInfo *playerInfo; PlayerEventHandler *playerEventHandler; PlayerActions *playerActions; - PlayerMenu *playerMenu; - PlayerGraphicsItem *graphicsItem; bool active; bool conceded; @@ -246,13 +239,10 @@ private: int zoneId; QMap zones; - QMap counters; - QMap arrows; + QMap counters; bool dialogSemaphore; QList cardsToDelete; - - // void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); }; class AnnotationDialog : public QInputDialog diff --git a/cockatrice/src/game/player/player_manager.cpp b/cockatrice/src/game/player/player_manager.cpp index e283d2196..6772d3ff1 100644 --- a/cockatrice/src/game/player/player_manager.cpp +++ b/cockatrice/src/game/player/player_manager.cpp @@ -1,35 +1,38 @@ #include "player_manager.h" #include "../abstract_game.h" -#include "player.h" +#include "player_logic.h" PlayerManager::PlayerManager(AbstractGame *_game, int _localPlayerId, bool _localPlayerIsJudge, bool localPlayerIsSpectator) - : QObject(_game), game(_game), players(QMap()), localPlayerId(_localPlayerId), + : QObject(_game), game(_game), players(QMap()), localPlayerId(_localPlayerId), localPlayerIsJudge(_localPlayerIsJudge), localPlayerIsSpectator(localPlayerIsSpectator) { } bool PlayerManager::isMainPlayerConceded() const { - Player *player = players.value(localPlayerId, nullptr); + PlayerLogic *player = players.value(localPlayerId, nullptr); return player && player->getConceded(); } -Player *PlayerManager::getActiveLocalPlayer(int activePlayer) const +PlayerLogic *PlayerManager::getActiveLocalPlayer(int activePlayer) const { - Player *active = players.value(activePlayer, 0); - if (active) - if (active->getPlayerInfo()->getLocal()) + PlayerLogic *active = players.value(activePlayer, 0); + if (active) { + if (active->getPlayerInfo()->getLocal()) { return active; + } + } - QMapIterator playerIterator(players); + QMapIterator playerIterator(players); while (playerIterator.hasNext()) { - Player *temp = playerIterator.next().value(); - if (temp->getPlayerInfo()->getLocal()) + PlayerLogic *temp = playerIterator.next().value(); + if (temp->getPlayerInfo()->getLocal()) { return temp; + } } return nullptr; @@ -40,11 +43,11 @@ bool PlayerManager::isLocalPlayer(int playerId) return game->getGameState()->getIsLocalGame() || playerId == localPlayerId; } -Player *PlayerManager::addPlayer(int playerId, const ServerInfo_User &info) +PlayerLogic *PlayerManager::addPlayer(int playerId, const ServerInfo_User &info) { - auto *newPlayer = new Player(info, playerId, isLocalPlayer(playerId) || game->getGameState()->getIsLocalGame(), - isJudge(), getGame()); - connect(newPlayer, &Player::concededChanged, this, &PlayerManager::onPlayerConceded); + auto *newPlayer = new PlayerLogic(info, playerId, isLocalPlayer(playerId) || game->getGameState()->getIsLocalGame(), + isJudge(), getGame()); + connect(newPlayer, &PlayerLogic::concededChanged, this, &PlayerManager::onPlayerConceded); players.insert(playerId, newPlayer); emit playerAdded(newPlayer); emit playerCountChanged(); @@ -53,7 +56,7 @@ Player *PlayerManager::addPlayer(int playerId, const ServerInfo_User &info) void PlayerManager::removePlayer(int playerId) { - Player *player = getPlayer(playerId); + PlayerLogic *player = getPlayer(playerId); if (!player) { return; } @@ -63,11 +66,12 @@ void PlayerManager::removePlayer(int playerId) player->deleteLater(); } -Player *PlayerManager::getPlayer(int playerId) const +PlayerLogic *PlayerManager::getPlayer(int playerId) const { - Player *player = players.value(playerId, 0); - if (!player) + PlayerLogic *player = players.value(playerId, 0); + if (!player) { return nullptr; + } return player; } diff --git a/cockatrice/src/game/player/player_manager.h b/cockatrice/src/game/player/player_manager.h index 0f813b3e4..2f8b87af8 100644 --- a/cockatrice/src/game/player/player_manager.h +++ b/cockatrice/src/game/player/player_manager.h @@ -1,8 +1,8 @@ /** * @file player_manager.h * @ingroup GameLogicPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_MANAGER_H #define COCKATRICE_PLAYER_MANAGER_H @@ -12,7 +12,7 @@ #include class AbstractGame; -class Player; +class PlayerLogic; class PlayerManager : public QObject { Q_OBJECT @@ -21,7 +21,7 @@ public: PlayerManager(AbstractGame *_game, int _localPlayerId, bool _localPlayerIsJudge, bool localPlayerIsSpectator); AbstractGame *game; - QMap players; + QMap players; int localPlayerId; bool localPlayerIsJudge; bool localPlayerIsSpectator; @@ -42,7 +42,7 @@ public: return localPlayerId; } - [[nodiscard]] const QMap &getPlayers() const + [[nodiscard]] const QMap &getPlayers() const { return players; } @@ -52,14 +52,14 @@ public: return players.size(); } - [[nodiscard]] Player *getActiveLocalPlayer(int activePlayer) const; + [[nodiscard]] PlayerLogic *getActiveLocalPlayer(int activePlayer) const; bool isLocalPlayer(int playerId); - Player *addPlayer(int playerId, const ServerInfo_User &info); + PlayerLogic *addPlayer(int playerId, const ServerInfo_User &info); void removePlayer(int playerId); - [[nodiscard]] Player *getPlayer(int playerId) const; + [[nodiscard]] PlayerLogic *getPlayer(int playerId) const; void onPlayerConceded(int playerId, bool conceded); @@ -106,8 +106,8 @@ public: } signals: - void playerAdded(Player *player); - void playerRemoved(Player *player); + void playerAdded(PlayerLogic *player); + void playerRemoved(PlayerLogic *player); void activeLocalPlayerConceded(); void activeLocalPlayerUnconceded(); void playerConceded(int playerId); diff --git a/cockatrice/src/game/replay.cpp b/cockatrice/src/game/replay.cpp index 6886f817a..69f9d8b20 100644 --- a/cockatrice/src/game/replay.cpp +++ b/cockatrice/src/game/replay.cpp @@ -2,9 +2,9 @@ #include "../interface/widgets/tabs/tab_game.h" -Replay::Replay(TabGame *_tab, GameReplay *_replay) : AbstractGame(_tab) +Replay::Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) { - gameState = new GameState(this, 0, -1, tab->getTabSupervisor()->getIsLocalGame(), {}, false, false, -1, false); + gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); playerManager = new PlayerManager(this, -1, false, true); loadReplay(_replay); diff --git a/cockatrice/src/game/replay.h b/cockatrice/src/game/replay.h index 301f47580..ecb3a10d0 100644 --- a/cockatrice/src/game/replay.h +++ b/cockatrice/src/game/replay.h @@ -2,8 +2,8 @@ * @file replay.h * @ingroup GameLogic * @ingroup Replay - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_REPLAY_H #define COCKATRICE_REPLAY_H @@ -15,7 +15,7 @@ class Replay : public AbstractGame Q_OBJECT public: - explicit Replay(TabGame *_tab, GameReplay *_replay); + explicit Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame); }; #endif // COCKATRICE_REPLAY_H diff --git a/cockatrice/src/game/zones/logic/card_zone_algorithms.h b/cockatrice/src/game/zones/card_zone_algorithms.h similarity index 100% rename from cockatrice/src/game/zones/logic/card_zone_algorithms.h rename to cockatrice/src/game/zones/card_zone_algorithms.h diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/card_zone_logic.cpp similarity index 91% rename from cockatrice/src/game/zones/logic/card_zone_logic.cpp rename to cockatrice/src/game/zones/card_zone_logic.cpp index e917e4ad7..7e0585f4e 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/card_zone_logic.cpp @@ -1,9 +1,9 @@ #include "card_zone_logic.h" -#include "../../board/card_item.h" -#include "../../player/player.h" -#include "../../player/player_actions.h" -#include "../view_zone.h" +#include "../../game_graphics/board/card_item.h" +#include "../../game_graphics/zones/view_zone.h" +#include "../player/player_actions.h" +#include "../player/player_logic.h" #include "view_zone_logic.h" #include @@ -20,7 +20,7 @@ * @param _contentsKnown whether the cards in the zone are known to the client * @param parent the parent QObject. */ -CardZoneLogic::CardZoneLogic(Player *_player, +CardZoneLogic::CardZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, @@ -55,8 +55,9 @@ void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x, emit cardAdded(card); addCardImpl(card, x, y); - if (reorganize) + if (reorganize) { emit reorganizeCards(); + } emit cardCountChanged(); } @@ -66,16 +67,19 @@ CardItem *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone) if (position == -1) { // position == -1 means either that the zone is indexed by card id // or that it doesn't matter which card you take. - for (int i = 0; i < cards.size(); ++i) + for (int i = 0; i < cards.size(); ++i) { if (cards[i]->getId() == cardId) { position = i; break; } - if (position == -1) + } + if (position == -1) { position = 0; + } } - if (position >= cards.size()) + if (position >= cards.size()) { return nullptr; + } for (auto *view : views) { qobject_cast(view->getLogic())->removeCard(position, toNewZone); @@ -142,8 +146,9 @@ void CardZoneLogic::moveAllToZone() cmd.set_target_zone(targetZone.toStdString()); cmd.set_x(targetX); - for (int i = 0; i < cards.size(); ++i) + for (int i = 0; i < cards.size(); ++i) { cmd.mutable_cards_to_move()->add_card()->set_card_id(cards[i]->getId()); + } player->getPlayerActions()->sendGameCommand(cmd); } @@ -175,9 +180,9 @@ void CardZoneLogic::clearContents() QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) const { QString ownerName = player->getPlayerInfo()->getName(); - if (name == ZoneNames::HAND) + if (name == ZoneNames::HAND) { return (theirOwn ? tr("their hand", "nominative") : tr("%1's hand", "nominative").arg(ownerName)); - else if (name == ZoneNames::DECK) + } else if (name == ZoneNames::DECK) { switch (gc) { case CaseLookAtZone: return (theirOwn ? tr("their library", "look at zone") @@ -193,11 +198,11 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons default: return (theirOwn ? tr("their library", "nominative") : tr("%1's library", "nominative").arg(ownerName)); } - else if (name == ZoneNames::GRAVE) + } else if (name == ZoneNames::GRAVE) { return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName)); - else if (name == ZoneNames::EXILE) + } else if (name == ZoneNames::EXILE) { return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName)); - else if (name == ZoneNames::SIDEBOARD) + } else if (name == ZoneNames::SIDEBOARD) { switch (gc) { case CaseLookAtZone: return (theirOwn ? tr("their sideboard", "look at zone") @@ -208,7 +213,7 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons default: break; } - else { + } else { return (theirOwn ? tr("their custom zone '%1'", "nominative").arg(name) : tr("%1's custom zone '%2'", "nominative").arg(ownerName).arg(name)); } diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.h b/cockatrice/src/game/zones/card_zone_logic.h similarity index 91% rename from cockatrice/src/game/zones/logic/card_zone_logic.h rename to cockatrice/src/game/zones/card_zone_logic.h index 1aa46effd..ae97719a9 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.h +++ b/cockatrice/src/game/zones/card_zone_logic.h @@ -1,21 +1,21 @@ /** * @file card_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_CARD_ZONE_LOGIC_H #define COCKATRICE_CARD_ZONE_LOGIC_H -#include "../../../client/translation.h" -#include "../../board/card_list.h" +#include "../../client/translation.h" +#include "../board/card_list.h" #include #include inline Q_LOGGING_CATEGORY(CardZoneLogicLog, "card_zone_logic"); -class Player; +class PlayerLogic; class ZoneViewZone; class QMenu; class QAction; @@ -35,7 +35,7 @@ signals: void retranslateUi(); public: - explicit CardZoneLogic(Player *_player, + explicit CardZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, @@ -68,7 +68,7 @@ public: return name; } [[nodiscard]] QString getTranslatedName(bool theirOwn, GrammaticalCase gc) const; - [[nodiscard]] Player *getPlayer() const + [[nodiscard]] PlayerLogic *getPlayer() const { return player; } @@ -105,7 +105,7 @@ private slots: void refreshCardInfos(); protected: - Player *player; + PlayerLogic *player; QString name; CardList cards; QList views; diff --git a/cockatrice/src/game/zones/logic/hand_zone_logic.cpp b/cockatrice/src/game/zones/hand_zone_logic.cpp similarity index 84% rename from cockatrice/src/game/zones/logic/hand_zone_logic.cpp rename to cockatrice/src/game/zones/hand_zone_logic.cpp index efa85d649..3bdd15902 100644 --- a/cockatrice/src/game/zones/logic/hand_zone_logic.cpp +++ b/cockatrice/src/game/zones/hand_zone_logic.cpp @@ -1,9 +1,9 @@ #include "hand_zone_logic.h" -#include "../../board/card_item.h" +#include "../../game_graphics/board/card_item.h" #include "card_zone_algorithms.h" -HandZoneLogic::HandZoneLogic(Player *_player, +HandZoneLogic::HandZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/logic/hand_zone_logic.h b/cockatrice/src/game/zones/hand_zone_logic.h similarity index 88% rename from cockatrice/src/game/zones/logic/hand_zone_logic.h rename to cockatrice/src/game/zones/hand_zone_logic.h index 8f34cc851..400506248 100644 --- a/cockatrice/src/game/zones/logic/hand_zone_logic.h +++ b/cockatrice/src/game/zones/hand_zone_logic.h @@ -1,8 +1,8 @@ /** * @file hand_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_HAND_ZONE_LOGIC_H #define COCKATRICE_HAND_ZONE_LOGIC_H @@ -12,7 +12,7 @@ class HandZoneLogic : public CardZoneLogic { Q_OBJECT public: - HandZoneLogic(Player *_player, + HandZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/logic/pile_zone_logic.cpp b/cockatrice/src/game/zones/pile_zone_logic.cpp similarity index 86% rename from cockatrice/src/game/zones/logic/pile_zone_logic.cpp rename to cockatrice/src/game/zones/pile_zone_logic.cpp index c5ffd5fd5..0f374fb84 100644 --- a/cockatrice/src/game/zones/logic/pile_zone_logic.cpp +++ b/cockatrice/src/game/zones/pile_zone_logic.cpp @@ -1,8 +1,8 @@ #include "pile_zone_logic.h" -#include "../../board/card_item.h" +#include "../../game_graphics/board/card_item.h" -PileZoneLogic::PileZoneLogic(Player *_player, +PileZoneLogic::PileZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, @@ -25,8 +25,9 @@ void PileZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) card->setCardRef({}); card->setId(-1); // If we obscure a previously revealed card, its name has to be forgotten - if (cards.size() > x + 1) + if (cards.size() > x + 1) { cards.at(x + 1)->setCardRef({}); + } } card->setVisible(false); card->resetState(); diff --git a/cockatrice/src/game/zones/logic/pile_zone_logic.h b/cockatrice/src/game/zones/pile_zone_logic.h similarity index 89% rename from cockatrice/src/game/zones/logic/pile_zone_logic.h rename to cockatrice/src/game/zones/pile_zone_logic.h index 498d0d4a2..522d99b89 100644 --- a/cockatrice/src/game/zones/logic/pile_zone_logic.h +++ b/cockatrice/src/game/zones/pile_zone_logic.h @@ -1,8 +1,8 @@ /** * @file pile_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PILE_ZONE_LOGIC_H #define COCKATRICE_PILE_ZONE_LOGIC_H @@ -17,7 +17,7 @@ signals: void callUpdate(); public: - PileZoneLogic(Player *_player, + PileZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/select_zone.cpp b/cockatrice/src/game/zones/select_zone.cpp deleted file mode 100644 index 9bf5f9faf..000000000 --- a/cockatrice/src/game/zones/select_zone.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "select_zone.h" - -#include "../../client/settings/cache_settings.h" -#include "../board/card_item.h" -#include "../game_scene.h" - -#include -#include - -qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse) -{ - qreal cardMinOverlap = cardHeight * SettingsCache::instance().getStackCardOverlapPercent() / 100; - qreal desiredHeight = cardHeight * cardCount - cardMinOverlap * (cardCount - 1); - qreal y; - if (desiredHeight > totalHeight) { - if (reverse) { - y = index / ((totalHeight - cardHeight) / (cardCount - 1)); - } else { - y = index * (totalHeight - cardHeight) / (cardCount - 1); - } - } else { - qreal start = (totalHeight - desiredHeight) / 2; - if (reverse) { - if (index <= start) { - return 0; - } - y = (index - start) / (cardHeight - cardMinOverlap); - } else { - y = index * (cardHeight - cardMinOverlap) + start; - } - } - return y; -} - -SelectZone::SelectZone(CardZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_logic, parent) -{ -} - -void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - if (event->buttons().testFlag(Qt::LeftButton)) { - QPointF pos = event->pos(); - if (pos.x() < 0) - pos.setX(0); - QRectF br = boundingRect(); - if (pos.x() > br.width()) - pos.setX(br.width()); - if (pos.y() < 0) - pos.setY(0); - if (pos.y() > br.height()) - pos.setY(br.height()); - - QRectF selectionRect = QRectF(selectionOrigin, pos).normalized(); - for (auto card : getLogic()->getCards()) { - if (card->getAttachedTo() && card->getAttachedTo()->getZone() != getLogic()) { - continue; - } - - bool inRect = selectionRect.intersects(card->mapRectToParent(card->boundingRect())); - if (inRect && !cardsInSelectionRect.contains(card)) { - // selection has just expanded to cover the card - cardsInSelectionRect.insert(card); - card->setSelected(!card->isSelected()); - } else if (!inRect && cardsInSelectionRect.contains(card)) { - // selection has just shrunk to no longer cover the card - cardsInSelectionRect.remove(card); - card->setSelected(!card->isSelected()); - } - } - static_cast(scene())->resizeRubberBand( - deviceTransform(static_cast(scene())->getViewportTransform()).map(pos), - cardsInSelectionRect.size()); - event->accept(); - } -} - -void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - if (event->button() == Qt::LeftButton) { - if (!event->modifiers().testFlag(Qt::ControlModifier)) { - scene()->clearSelection(); - } - - selectionOrigin = event->pos(); - static_cast(scene())->startRubberBand(event->scenePos()); - event->accept(); - } else - CardZone::mousePressEvent(event); -} - -void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) -{ - selectionOrigin = QPoint(); - cardsInSelectionRect.clear(); - static_cast(scene())->stopRubberBand(); - event->accept(); -} diff --git a/cockatrice/src/game/zones/select_zone.h b/cockatrice/src/game/zones/select_zone.h deleted file mode 100644 index d6fd3e10e..000000000 --- a/cockatrice/src/game/zones/select_zone.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @file select_zone.h - * @ingroup GameGraphicsZones - * @brief TODO: Document this. - */ - -#ifndef SELECTZONE_H -#define SELECTZONE_H - -#include "card_zone.h" - -#include - -/** - * A CardZone where the cards are laid out, with each card directly interactable by clicking. - */ -class SelectZone : public CardZone -{ - Q_OBJECT -private: - QPointF selectionOrigin; - QSet cardsInSelectionRect; - -protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; - void mousePressEvent(QGraphicsSceneMouseEvent *event) override; - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - -public: - SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr); -}; - -qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse = false); - -#endif diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp deleted file mode 100644 index a9d649a4a..000000000 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "stack_zone.h" - -#include "../../client/settings/cache_settings.h" -#include "../../interface/theme_manager.h" -#include "../board/arrow_item.h" -#include "../board/card_drag_item.h" -#include "../board/card_item.h" -#include "../player/player.h" -#include "../player/player_actions.h" -#include "logic/stack_zone_logic.h" - -#include -#include - -StackZone::StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent) - : SelectZone(_logic, parent), zoneHeight(_zoneHeight) -{ - connect(themeManager, &ThemeManager::themeChanged, this, &StackZone::updateBg); - updateBg(); - setCacheMode(DeviceCoordinateCache); -} - -void StackZone::updateBg() -{ - update(); -} - -QRectF StackZone::boundingRect() const -{ - return {0, 0, 100, zoneHeight}; -} - -void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) -{ - QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId()); - painter->fillRect(boundingRect(), brush); -} - -void StackZone::handleDropEvent(const QList &dragItems, - CardZoneLogic *startZone, - const QPoint &dropPoint) -{ - if (startZone == nullptr || startZone->getPlayer() == nullptr) { - return; - } - - Command_MoveCard cmd; - cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); - cmd.set_start_zone(startZone->getName().toStdString()); - cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId()); - cmd.set_target_zone(getLogic()->getName().toStdString()); - - int index = 0; - - if (!getLogic()->getCards().isEmpty()) { - const auto cardCount = static_cast(getLogic()->getCards().size()); - const auto &card = getLogic()->getCards().at(0); - - index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(), - card->boundingRect().height(), true)); - - // divideCardSpaceInZone is not guaranteed to return a valid index - // currently, so clamp it to avoid crashes. - index = qBound(0, index, cardCount - 1); - - if (startZone == getLogic()) { - const auto &dragItem = dragItems.at(0); - const auto &card = getLogic()->getCards().at(index); - - if (card->getId() == dragItem->getId()) { - return; - } - } - } - - cmd.set_x(index); - cmd.set_y(0); - - for (CardDragItem *item : dragItems) { - if (item) { - auto cardToMove = cmd.mutable_cards_to_move()->add_card(); - cardToMove->set_card_id(item->getId()); - if (item->isForceFaceDown()) { - cardToMove->set_face_down(true); - } - } - } - - getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd); -} - -void StackZone::reorganizeCards() -{ - if (!getLogic()->getCards().isEmpty()) { - const auto cardCount = static_cast(getLogic()->getCards().size()); - qreal totalWidth = boundingRect().width(); - qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width(); - qreal xspace = 5; - qreal x1 = xspace; - qreal x2 = totalWidth - xspace - cardWidth; - - for (int i = 0; i < cardCount; i++) { - CardItem *card = getLogic()->getCards().at(i); - qreal x = (i % 2) ? x2 : x1; - qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(), - getLogic()->getCards().at(0)->boundingRect().height()); - card->setPos(x, y); - card->setRealZValue(i); - } - } - update(); -} diff --git a/cockatrice/src/game/zones/logic/stack_zone_logic.cpp b/cockatrice/src/game/zones/stack_zone_logic.cpp similarity index 84% rename from cockatrice/src/game/zones/logic/stack_zone_logic.cpp rename to cockatrice/src/game/zones/stack_zone_logic.cpp index 6f3cb6e99..341d4b0e4 100644 --- a/cockatrice/src/game/zones/logic/stack_zone_logic.cpp +++ b/cockatrice/src/game/zones/stack_zone_logic.cpp @@ -1,9 +1,9 @@ #include "stack_zone_logic.h" -#include "../../board/card_item.h" +#include "../../game_graphics/board/card_item.h" #include "card_zone_algorithms.h" -StackZoneLogic::StackZoneLogic(Player *_player, +StackZoneLogic::StackZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/logic/stack_zone_logic.h b/cockatrice/src/game/zones/stack_zone_logic.h similarity index 88% rename from cockatrice/src/game/zones/logic/stack_zone_logic.h rename to cockatrice/src/game/zones/stack_zone_logic.h index 81b9d6969..cce4bd0fa 100644 --- a/cockatrice/src/game/zones/logic/stack_zone_logic.h +++ b/cockatrice/src/game/zones/stack_zone_logic.h @@ -1,8 +1,8 @@ /** * @file stack_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_STACK_ZONE_LOGIC_H #define COCKATRICE_STACK_ZONE_LOGIC_H @@ -12,7 +12,7 @@ class StackZoneLogic : public CardZoneLogic { Q_OBJECT public: - StackZoneLogic(Player *_player, + StackZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/logic/table_zone_logic.cpp b/cockatrice/src/game/zones/table_zone_logic.cpp similarity index 88% rename from cockatrice/src/game/zones/logic/table_zone_logic.cpp rename to cockatrice/src/game/zones/table_zone_logic.cpp index 42caf2ec8..a4f033819 100644 --- a/cockatrice/src/game/zones/logic/table_zone_logic.cpp +++ b/cockatrice/src/game/zones/table_zone_logic.cpp @@ -1,8 +1,8 @@ #include "table_zone_logic.h" -#include "../../board/card_item.h" +#include "../../game_graphics/board/card_item.h" -TableZoneLogic::TableZoneLogic(Player *_player, +TableZoneLogic::TableZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, @@ -29,7 +29,8 @@ CardItem *TableZoneLogic::takeCard(int position, int cardId, bool toNewZone) { CardItem *result = CardZoneLogic::takeCard(position, cardId); - if (toNewZone) + if (toNewZone) { emit contentSizeChanged(); + } return result; } \ No newline at end of file diff --git a/cockatrice/src/game/zones/logic/table_zone_logic.h b/cockatrice/src/game/zones/table_zone_logic.h similarity index 93% rename from cockatrice/src/game/zones/logic/table_zone_logic.h rename to cockatrice/src/game/zones/table_zone_logic.h index e173bc637..6d8d64a20 100644 --- a/cockatrice/src/game/zones/logic/table_zone_logic.h +++ b/cockatrice/src/game/zones/table_zone_logic.h @@ -1,8 +1,8 @@ /** * @file table_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_TABLE_ZONE_LOGIC_H #define COCKATRICE_TABLE_ZONE_LOGIC_H @@ -16,7 +16,7 @@ signals: void toggleTapped(); public: - TableZoneLogic(Player *_player, + TableZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, diff --git a/cockatrice/src/game/zones/logic/view_zone_logic.cpp b/cockatrice/src/game/zones/view_zone_logic.cpp similarity index 97% rename from cockatrice/src/game/zones/logic/view_zone_logic.cpp rename to cockatrice/src/game/zones/view_zone_logic.cpp index 5a4db3163..8782a1762 100644 --- a/cockatrice/src/game/zones/logic/view_zone_logic.cpp +++ b/cockatrice/src/game/zones/view_zone_logic.cpp @@ -1,7 +1,7 @@ #include "view_zone_logic.h" -#include "../../../client/settings/cache_settings.h" -#include "../../board/card_item.h" +#include "../../client/settings/cache_settings.h" +#include "../../game_graphics/board/card_item.h" /** * @param _player the player that the cards are revealed to. @@ -9,7 +9,7 @@ * @param _revealZone if false, the cards will be face down. * @param _writeableRevealZone whether the player can interact with the revealed cards. */ -ZoneViewZoneLogic::ZoneViewZoneLogic(Player *_player, +ZoneViewZoneLogic::ZoneViewZoneLogic(PlayerLogic *_player, CardZoneLogic *_origZone, int _numberCards, bool _revealZone, diff --git a/cockatrice/src/game/zones/logic/view_zone_logic.h b/cockatrice/src/game/zones/view_zone_logic.h similarity index 95% rename from cockatrice/src/game/zones/logic/view_zone_logic.h rename to cockatrice/src/game/zones/view_zone_logic.h index 6bd5ecc8d..04ed20e45 100644 --- a/cockatrice/src/game/zones/logic/view_zone_logic.h +++ b/cockatrice/src/game/zones/view_zone_logic.h @@ -1,8 +1,8 @@ /** * @file view_zone_logic.h * @ingroup GameLogicZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_VIEW_ZONE_LOGIC_H #define COCKATRICE_VIEW_ZONE_LOGIC_H @@ -30,7 +30,7 @@ public: REMOVE_CARD }; - ZoneViewZoneLogic(Player *_player, + ZoneViewZoneLogic(PlayerLogic *_player, CardZoneLogic *_origZone, int _numberCards, bool _revealZone, diff --git a/cockatrice/src/game/board/abstract_card_drag_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp similarity index 98% rename from cockatrice/src/game/board/abstract_card_drag_item.cpp rename to cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp index 8e3def4ca..026efd60d 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_drag_item.cpp @@ -25,11 +25,12 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, setCursor(Qt::ClosedHandCursor); setZValue(ZValues::DRAG_ITEM); } - if (item->getTapped()) + if (item->getTapped()) { setTransform(QTransform() .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(90) .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); + } setCacheMode(DeviceCoordinateCache); diff --git a/cockatrice/src/game/board/abstract_card_drag_item.h b/cockatrice/src/game_graphics/board/abstract_card_drag_item.h similarity index 97% rename from cockatrice/src/game/board/abstract_card_drag_item.h rename to cockatrice/src/game_graphics/board/abstract_card_drag_item.h index fe3b87983..1cbeb4fe7 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.h +++ b/cockatrice/src/game_graphics/board/abstract_card_drag_item.h @@ -1,8 +1,8 @@ /** * @file abstract_card_drag_item.h * @ingroup GameGraphicsCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ABSTRACTCARDDRAGITEM_H #define ABSTRACTCARDDRAGITEM_H diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp similarity index 90% rename from cockatrice/src/game/board/abstract_card_item.cpp rename to cockatrice/src/game_graphics/board/abstract_card_item.cpp index 9ec6ada9a..86b3e27c8 100644 --- a/cockatrice/src/game/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -13,7 +13,7 @@ #include #include -AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, Player *_owner, int _id) +AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id) : ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0), bgColor(Qt::transparent), isHovered(false), realZValue(0) { @@ -85,7 +85,12 @@ const CardInfo &AbstractCardItem::getCardInfo() const void AbstractCardItem::setRealZValue(qreal _zValue) { realZValue = _zValue; - setZValue(_zValue); + // During hover, zValue is overridden to HOVERED_CARD. Layout operations + // like reorganizeCards() call setRealZValue() on all cards including the + // hovered one — skip setZValue() here to avoid clobbering the override. + if (!isHovered) { + setZValue(_zValue); + } } QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const @@ -126,8 +131,9 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS // don't even spend time trying to load the picture if our size is too small if (translatedSize.width() > 10) { CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize()); - if (translatedPixmap.isNull()) + if (translatedPixmap.isNull()) { paintImage = false; + } } else { paintImage = false; } @@ -152,9 +158,9 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS painter->setBackground(Qt::black); painter->setBackgroundMode(Qt::OpaqueMode); QString nameStr; - if (facedown) + if (facedown) { nameStr = "# " + QString::number(id); - else { + } else { QString prefix = ""; if (SettingsCache::instance().debug().getShowCardId()) { prefix = "#" + QString::number(id) + " "; @@ -181,10 +187,12 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * if (isSelected() || isHovered) { QPen pen; - if (isHovered) + if (isHovered) { pen.setColor(Qt::yellow); - if (isSelected()) + } + if (isSelected()) { pen.setColor(Qt::red); + } pen.setWidth(0); // Cosmetic pen painter->setPen(pen); painter->drawPath(shape()); @@ -210,11 +218,20 @@ void AbstractCardItem::setCardRef(const CardRef &_cardRef) void AbstractCardItem::setHovered(bool _hovered) { - if (isHovered == _hovered) + if (isHovered == _hovered) { return; + } - if (_hovered) + if (_hovered) { processHoverEvent(); + } else { + // Mark the hovered card's current scene footprint dirty so overlapped + // sibling zones (e.g. StackZone) repaint after the card moves away. + if (scene()) { + scene()->update(sceneBoundingRect()); + } + } + isHovered = _hovered; setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue); setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); @@ -265,13 +282,14 @@ void AbstractCardItem::cacheBgColor() void AbstractCardItem::setTapped(bool _tapped, bool canAnimate) { - if (tapped == _tapped) + if (tapped == _tapped) { return; + } tapped = _tapped; - if (SettingsCache::instance().getTapAnimation() && canAnimate) + if (SettingsCache::instance().getTapAnimation() && canAnimate) { static_cast(scene())->registerAnimationItem(this); - else { + } else { tapAngle = tapped ? 90 : 0; setTransform(QTransform() .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) @@ -297,17 +315,19 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) scene()->clearSelection(); setSelected(true); } - if (event->button() == Qt::LeftButton) + if (event->button() == Qt::LeftButton) { setCursor(Qt::ClosedHandCursor); - else if (event->button() == Qt::MiddleButton) + } else if (event->button() == Qt::MiddleButton) { emit showCardInfoPopup(event->screenPos(), cardRef); + } event->accept(); } void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::MiddleButton) + if (event->button() == Qt::MiddleButton) { emit deleteCardInfoPopup(cardRef.name); + } // This function ensures the parent function doesn't mess around with our selection. event->accept(); @@ -323,6 +343,7 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, if (change == ItemSelectedHasChanged) { update(); return value; - } else + } else { return ArrowTarget::itemChange(change, value); + } } diff --git a/cockatrice/src/game/board/abstract_card_item.h b/cockatrice/src/game_graphics/board/abstract_card_item.h similarity index 83% rename from cockatrice/src/game/board/abstract_card_item.h rename to cockatrice/src/game_graphics/board/abstract_card_item.h index 7d2c29cae..bdb5f7cf1 100644 --- a/cockatrice/src/game/board/abstract_card_item.h +++ b/cockatrice/src/game_graphics/board/abstract_card_item.h @@ -1,20 +1,20 @@ /** * @file abstract_card_item.h * @ingroup GameGraphicsCards - * @brief TODO: Document this. + * @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic. */ #ifndef ABSTRACTCARDITEM_H #define ABSTRACTCARDITEM_H -#include "../../game_graphics/board/graphics_item_type.h" #include "../card_dimensions.h" #include "arrow_target.h" +#include "graphics_item_type.h" #include #include -class Player; +class PlayerLogic; class AbstractCardItem : public ArrowTarget { @@ -44,6 +44,11 @@ signals: void deleteCardInfoPopup(QString cardName); void sigPixmapUpdated(); void cardShiftClicked(QString cardName); + void rightClicked(AbstractCardItem *card, QPoint screenPos); + void playSelected(AbstractCardItem *card); + void playSelectedFaceDown(AbstractCardItem *card); + void hideSelected(AbstractCardItem *card); + void selectionChanged(AbstractCardItem *card, bool selected); public: enum @@ -56,7 +61,7 @@ public: } explicit AbstractCardItem(QGraphicsItem *parent = nullptr, const CardRef &cardRef = {}, - Player *_owner = nullptr, + PlayerLogic *_owner = nullptr, int _id = -1); ~AbstractCardItem() override; QRectF boundingRect() const override; @@ -96,6 +101,10 @@ public: } void setRealZValue(qreal _zValue); void setHovered(bool _hovered); + bool getIsHovered() const + { + return isHovered; + } QString getColor() const { return color; diff --git a/cockatrice/src/game/board/abstract_counter.cpp b/cockatrice/src/game_graphics/board/abstract_counter.cpp similarity index 55% rename from cockatrice/src/game/board/abstract_counter.cpp rename to cockatrice/src/game_graphics/board/abstract_counter.cpp index 08d19ec8a..219dd456e 100644 --- a/cockatrice/src/game/board/abstract_counter.cpp +++ b/cockatrice/src/game_graphics/board/abstract_counter.cpp @@ -1,14 +1,15 @@ #include "abstract_counter.h" #include "../../client/settings/cache_settings.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game_graphics/board/translate_counter_name.h" #include "../../interface/widgets/tabs/tab_game.h" -#include "../player/player.h" -#include "../player/player_actions.h" -#include "translate_counter_name.h" #include #include #include +#include #include #include #include @@ -16,24 +17,24 @@ #include #include -AbstractCounter::AbstractCounter(Player *_player, - int _id, - const QString &_name, +AbstractCounter::AbstractCounter(CounterState *state, + PlayerLogic *_player, bool _shownInCounterArea, - int _value, bool _useNameForShortcut, QGraphicsItem *parent) - : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), - useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false), - deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea) + : QGraphicsItem(parent), player(_player), id(state->getId()), name(state->getName()), value(state->getValue()), + color(state->getColor()), radius(state->getRadius()), useNameForShortcut(_useNameForShortcut), + shownInCounterArea(_shownInCounterArea) { setAcceptHoverEvents(true); - shortcutActive = false; + connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { + value = newValue; + update(); + }); if (player->getPlayerInfo()->getLocalOrJudge()) { - QString displayName = TranslateCounterName::getDisplayName(_name); - menu = new TearOffMenu(displayName); + menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName())); aSet = new QAction(this); connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter); menu->addAction(aSet); @@ -41,16 +42,18 @@ AbstractCounter::AbstractCounter(Player *_player, for (int i = 10; i >= -10; --i) { if (i == 0) { menu->addSeparator(); - } else { - QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this); - if (i == -1) - aDec = aIncrement; - else if (i == 1) - aInc = aIncrement; - aIncrement->setData(i); - connect(aIncrement, &QAction::triggered, this, &AbstractCounter::incrementCounter); - menu->addAction(aIncrement); + continue; } + auto *a = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this); + if (i == -1) { + aDec = a; + } + if (i == 1) { + aInc = a; + } + a->setData(i); + connect(a, &QAction::triggered, this, &AbstractCounter::incrementCounter); + menu->addAction(a); } } else { menu = nullptr; @@ -69,39 +72,35 @@ AbstractCounter::~AbstractCounter() void AbstractCounter::delCounter() { - if (dialogSemaphore) + if (dialogSemaphore) { deleteAfterDialog = true; - else + } else { deleteLater(); + } } void AbstractCounter::retranslateUi() { - if (menu) { + if (aSet) { aSet->setText(tr("&Set counter...")); } } void AbstractCounter::setShortcutsActive() { - if (!menu) { + if (!menu || !player->getPlayerInfo()->getLocal()) { return; } - if (!player->getPlayerInfo()->getLocal()) { - return; - } - - ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + ShortcutsSettings &sc = SettingsCache::instance().shortcuts(); + shortcutActive = true; if (name == "life") { - shortcutActive = true; - aSet->setShortcuts(shortcuts.getShortcut("Player/aSet")); - aDec->setShortcuts(shortcuts.getShortcut("Player/aDec")); - aInc->setShortcuts(shortcuts.getShortcut("Player/aInc")); + aSet->setShortcuts(sc.getShortcut("Player/aSet")); + aDec->setShortcuts(sc.getShortcut("Player/aDec")); + aInc->setShortcuts(sc.getShortcut("Player/aInc")); } else if (useNameForShortcut) { - shortcutActive = true; - aSet->setShortcuts(shortcuts.getShortcut("Player/aSetCounter_" + name)); - aDec->setShortcuts(shortcuts.getShortcut("Player/aDecCounter_" + name)); - aInc->setShortcuts(shortcuts.getShortcut("Player/aIncCounter_" + name)); + aSet->setShortcuts(sc.getShortcut("Player/aSetCounter_" + name)); + aDec->setShortcuts(sc.getShortcut("Player/aDecCounter_" + name)); + aInc->setShortcuts(sc.getShortcut("Player/aIncCounter_" + name)); } } @@ -126,43 +125,32 @@ void AbstractCounter::refreshShortcuts() } } -void AbstractCounter::setValue(int _value) -{ - value = _value; - update(); -} - void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (isUnderMouse() && player->getPlayerInfo()->getLocalOrJudge()) { - if (event->button() == Qt::MiddleButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { - if (menu) - menu->exec(event->screenPos()); - event->accept(); - } else if (event->button() == Qt::LeftButton) { - Command_IncCounter cmd; - cmd.set_counter_id(id); - cmd.set_delta(1); - player->getPlayerActions()->sendGameCommand(cmd); - event->accept(); - } else if (event->button() == Qt::RightButton) { - Command_IncCounter cmd; - cmd.set_counter_id(id); - cmd.set_delta(-1); - player->getPlayerActions()->sendGameCommand(cmd); - event->accept(); - } - } else + if (!isUnderMouse() || !player->getPlayerInfo()->getLocalOrJudge()) { event->ignore(); + return; + } + + if (event->button() == Qt::MiddleButton || QApplication::keyboardModifiers() & Qt::ShiftModifier) { + if (menu) { + menu->exec(event->screenPos()); + } + } else { + Command_IncCounter cmd; + cmd.set_counter_id(id); + cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1); + player->getPlayerActions()->sendGameCommand(cmd); + } + event->accept(); } -void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/) +void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent *) { hovered = true; update(); } - -void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/) +void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { hovered = false; update(); @@ -170,34 +158,36 @@ void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/) void AbstractCounter::incrementCounter() { - const int delta = static_cast(sender())->data().toInt(); Command_IncCounter cmd; cmd.set_counter_id(id); - cmd.set_delta(delta); + cmd.set_delta(static_cast(sender())->data().toInt()); player->getPlayerActions()->sendGameCommand(cmd); } void AbstractCounter::setCounter() { + QWidget *parent = nullptr; + if (auto *view = scene() ? scene()->views().value(0) : nullptr) { + parent = view->window(); + } + dialogSemaphore = true; - AbstractCounterDialog dialog(name, QString::number(value), player->getGame()->getTab()); - const int ok = dialog.exec(); + AbstractCounterDialog dlg(name, QString::number(value), parent); + const int ok = dlg.exec(); + dialogSemaphore = false; if (deleteAfterDialog) { deleteLater(); return; } - dialogSemaphore = false; - - if (!ok) + if (!ok) { return; + } Expression exp(value); - int newValue = static_cast(exp.parse(dialog.textValue())); - Command_SetCounter cmd; cmd.set_counter_id(id); - cmd.set_value(newValue); + cmd.set_value(static_cast(exp.parse(dlg.textValue()))); player->getPlayerActions()->sendGameCommand(cmd); } @@ -231,8 +221,9 @@ void AbstractCounterDialog::changeValue(int diff) { bool ok; int curValue = textValue().toInt(&ok); - if (!ok) + if (!ok) { return; + } curValue += diff; setTextValue(QString::number(curValue)); } diff --git a/cockatrice/src/game/board/abstract_counter.h b/cockatrice/src/game_graphics/board/abstract_counter.h similarity index 70% rename from cockatrice/src/game/board/abstract_counter.h rename to cockatrice/src/game_graphics/board/abstract_counter.h index 074650d54..b319a722d 100644 --- a/cockatrice/src/game/board/abstract_counter.h +++ b/cockatrice/src/game_graphics/board/abstract_counter.h @@ -1,19 +1,20 @@ /** * @file abstract_counter.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COUNTER_H #define COUNTER_H +#include "../../game/board/counter_state.h" #include "../../interface/widgets/menus/tearoff_menu.h" #include "../player/menu/abstract_player_component.h" #include #include -class Player; +class PlayerLogic; class QAction; class QKeyEvent; class QMenu; @@ -25,22 +26,26 @@ class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPla Q_INTERFACES(QGraphicsItem) protected: - Player *player; + PlayerLogic *player; int id; QString name; int value; - bool useNameForShortcut, hovered; + QColor color; + int radius; + bool hovered = false; + bool useNameForShortcut; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; private: - QAction *aSet, *aDec, *aInc; - TearOffMenu *menu; - bool dialogSemaphore, deleteAfterDialog; + QAction *aSet = nullptr, *aDec = nullptr, *aInc = nullptr; + TearOffMenu *menu = nullptr; + bool dialogSemaphore = false; + bool deleteAfterDialog = false; bool shownInCounterArea; - bool shortcutActive; + bool shortcutActive = false; private slots: void refreshShortcuts(); @@ -48,17 +53,14 @@ private slots: void setCounter(); public: - AbstractCounter(Player *_player, - int _id, - const QString &_name, - bool _shownInCounterArea, - int _value, - bool _useNameForShortcut = false, + AbstractCounter(CounterState *state, + PlayerLogic *player, + bool shownInCounterArea, + bool useNameForShortcut = false, QGraphicsItem *parent = nullptr); ~AbstractCounter() override; void retranslateUi() override; - void setValue(int _value); void setShortcutsActive() override; void setShortcutsInactive() override; void delCounter(); @@ -67,7 +69,6 @@ public: { return menu; } - int getId() const { return id; @@ -76,14 +77,22 @@ public: { return name; } - bool getShownInCounterArea() const + QColor getColor() const { - return shownInCounterArea; + return color; + } + int getRadius() const + { + return radius; } int getValue() const { return value; } + bool getShownInCounterArea() const + { + return shownInCounterArea; + } }; class AbstractCounterDialog : public QInputDialog diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 05f4a41ab..8da8ab708 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -19,27 +19,29 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, QFontMetrics fm(font); double w = 1.3 * fm.horizontalAdvance(numStr); double h = fm.height() * 1.3; - if (w < h) + if (w < h) { w = h; + } painter->setPen(QColor(255, 255, 255, 0)); painter->setBrush(QBrush(QColor(color))); QRectF textRect; - if (position == -1) + if (position == -1) { textRect = QRectF((boundingRect().width() - w) / 2.0, (boundingRect().height() - h) / 2.0, w, h); - else { + } else { qreal xOffset = 10; qreal yOffset = 20; qreal spacing = 2; - if (position < 2) + if (position < 2) { textRect = QRectF(count == 1 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset, w, h); - else + } else { textRect = QRectF(count == 3 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset + (spacing + h) * (position / 2), w, h); + } } painter->drawEllipse(textRect); diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.h b/cockatrice/src/game_graphics/board/abstract_graphics_item.h index 93b9bff1b..287c2c0b3 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.h +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.h @@ -1,8 +1,8 @@ /** * @file abstract_graphics_item.h * @ingroup GameGraphics - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ABSTRACTGRAPHICSITEM_H #define ABSTRACTGRAPHICSITEM_H diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game_graphics/board/arrow_item.cpp similarity index 57% rename from cockatrice/src/game/board/arrow_item.cpp rename to cockatrice/src/game_graphics/board/arrow_item.cpp index 60585a774..af6a6bf36 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game_graphics/board/arrow_item.cpp @@ -2,8 +2,8 @@ #include "arrow_item.h" #include "../../client/settings/cache_settings.h" -#include "../player/player.h" -#include "../player/player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" #include "../player/player_target.h" #include "../z_values.h" #include "../zones/card_zone.h" @@ -21,46 +21,49 @@ #include #include -ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color) - : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false), - color(_color), fullColor(true) +ArrowItem::ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem) + : data(std::move(_data)), startItem(_startItem), targetItem(_targetItem) { setZValue(ZValues::ARROWS); - if (startItem) - startItem->addArrowFrom(this); - if (targetItem) - targetItem->addArrowTo(this); + auto doUpdate = [this]() { + if (startItem && targetItem) { + updatePath(); + } + }; - if (startItem && targetItem) + if (startItem) { + connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate); + connect(startItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed); + } + if (targetItem) { + connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate); + connect(targetItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed); + } + + if (startItem && targetItem) { updatePath(); + } } -ArrowItem::~ArrowItem() +void ArrowItem::onTargetDestroyed() { + emit requestDeletion(data->creatorId, data->id); } void ArrowItem::delArrow() { - if (startItem) { - startItem->removeArrowFrom(this); - startItem = 0; - } - if (targetItem) { targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - targetItem = 0; } - - player->removeArrow(this); deleteLater(); } void ArrowItem::updatePath() { - if (!targetItem) + if (!targetItem) { return; + } QPointF endPoint = targetItem->mapToScene( QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2)); @@ -75,8 +78,9 @@ void ArrowItem::updatePath(const QPointF &endPoint) headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++ const double phi = 15; - if (!startItem) + if (!startItem) { return; + } QPointF startPoint = startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2)); @@ -84,9 +88,9 @@ void ArrowItem::updatePath(const QPointF &endPoint) qreal lineLength = line.length(); prepareGeometryChange(); - if (lineLength < 30) + if (lineLength < 30) { path = QPainterPath(); - else { + } else { QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength); QPainterPath centerLine; @@ -122,24 +126,24 @@ void ArrowItem::updatePath(const QPointF &endPoint) void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QColor paintColor(color); - if (fullColor) + QColor paintColor(data->color); + if (fullColor) { paintColor.setAlpha(200); - else + } else { paintColor.setAlpha(150); + } painter->setBrush(paintColor); painter->drawPath(path); } void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (!player->getPlayerInfo()->getLocal()) { + if (!data->isLocalCreator) { event->ignore(); return; } - QList colliding = scene()->items(event->scenePos()); - for (QGraphicsItem *item : colliding) { + for (auto *item : scene()->items(event->scenePos())) { if (qgraphicsitem_cast(item)) { event->ignore(); return; @@ -148,95 +152,109 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) event->accept(); if (event->button() == Qt::RightButton) { - Command_DeleteArrow cmd; - cmd.set_arrow_id(id); - player->getPlayerActions()->sendGameCommand(cmd); + emit requestDeletion(data->creatorId, data->id); } } -ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) - : ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase) +// ArrowDragItem + +ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) + : ArrowItem(QSharedPointer::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(), + .isLocalCreator = true, + .id = -1, + .color = _color}), + _startItem, + nullptr), + player(_owner), deleteInPhase(_deleteInPhase) { } -void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow) +void ArrowDragItem::addChildArrow(ArrowDragItem *child) { - childArrows.append(childArrow); + childArrows.append(child); } void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - // This ensures that if a mouse move event happens after a call to delArrow(), - // the event will be discarded as it would create some stray pointers. - if (targetLocked || !startItem) + if (targetLocked || !startItem) { return; + } - QPointF endPos = event->scenePos(); + const QPointF endPos = event->scenePos(); - QList colliding = scene()->items(endPos); - ArrowTarget *cursorItem = 0; + ArrowTarget *cursorItem = nullptr; qreal cursorItemZ = -1; - for (int i = colliding.size() - 1; i >= 0; i--) { - if (qgraphicsitem_cast(colliding.at(i)) || qgraphicsitem_cast(colliding.at(i))) { - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); - } + for (auto *item : scene()->items(endPos)) { + ArrowTarget *candidate = nullptr; + if (auto *card = qgraphicsitem_cast(item)) { + candidate = card; + } else if (auto *pt = qgraphicsitem_cast(item)) { + candidate = pt; + } + + if (candidate && candidate->zValue() > cursorItemZ) { + cursorItem = candidate; + cursorItemZ = candidate->zValue(); } } - if ((cursorItem != targetItem) && targetItem) { - targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - } - if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - if (cursorItem != targetItem) { - fullColor = true; - if (cursorItem != startItem) { - cursorItem->setBeingPointedAt(true); - cursorItem->addArrowTo(this); - } - targetItem = cursorItem; + if (cursorItem != targetItem) { + if (targetItem) { + disconnect(positionConnection); + targetItem->setBeingPointedAt(false); + } + + targetItem = cursorItem; + fullColor = (cursorItem != nullptr); + + if (cursorItem && cursorItem != startItem) { + cursorItem->setBeingPointedAt(true); + positionConnection = + connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); } - updatePath(); } + + targetItem ? updatePath() : updatePath(endPos); update(); - for (ArrowDragItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseMoveEvent(event); } } void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (!startItem) + if (!startItem) { return; + } - if (targetItem && (targetItem != startItem)) { - CardZoneLogic *startZone = static_cast(startItem)->getZone(); + if (targetItem && targetItem != startItem) { + CardItem *startCard = qgraphicsitem_cast(startItem); // For now, we can safely assume that the start item is always a card. // The target item can be a player as well. - CardItem *startCard = qgraphicsitem_cast(startItem); - CardItem *targetCard = qgraphicsitem_cast(targetItem); + if (!startCard) { + delArrow(); + return; + } + + CardZoneLogic *startZone = startCard->getZone(); Command_CreateArrow cmd; - cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color)); + cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color)); cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_start_card_id(startCard->getId()); - if (targetCard) { + if (auto *targetCard = qgraphicsitem_cast(targetItem)) { CardZoneLogic *targetZone = targetCard->getZone(); cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone->getName().toStdString()); cmd.set_target_card_id(targetCard->getId()); - } else { // failed to cast target to card, this means it's a player - PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); + } else if (auto *targetPlayer = qgraphicsitem_cast(targetItem)) { cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId()); + } else { + delArrow(); + return; } // if the card is in hand then we will move the card to stack or table as part of drawing the arrow @@ -246,10 +264,11 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) bool playToStack = SettingsCache::instance().getPlayToStack(); if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) || (playToStack && ci->getUiAttributes().tableRow != 0 && - startCard->getZone()->getName() != ZoneNames::STACK))) + startCard->getZone()->getName() != ZoneNames::STACK))) { cmd.set_start_zone(ZoneNames::STACK); - else + } else { cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE); + } } if (deleteInPhase != 0) { @@ -258,111 +277,116 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) player->getPlayerActions()->sendGameCommand(cmd); } - delArrow(); - for (ArrowDragItem *child : childArrows) { + delArrow(); + for (auto *child : childArrows) { child->mouseReleaseEvent(event); } } +// ArrowAttachItem ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) - : ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green) + : ArrowItem( + QSharedPointer::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(), + .isLocalCreator = true, + .id = -1, + .color = Qt::green}), + _startItem, + nullptr), + player(_startItem->getOwner()) { } -void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow) +void ArrowAttachItem::addChildArrow(ArrowAttachItem *child) { - childArrows.append(childArrow); + childArrows.append(child); } void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (targetLocked || !startItem) + if (targetLocked || !startItem) { return; + } - QPointF endPos = event->scenePos(); + const QPointF endPos = event->scenePos(); - QList colliding = scene()->items(endPos); - ArrowTarget *cursorItem = 0; + ArrowTarget *cursorItem = nullptr; qreal cursorItemZ = -1; - for (int i = colliding.size() - 1; i >= 0; i--) { - if (qgraphicsitem_cast(colliding.at(i))) { - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); + for (auto *item : scene()->items(endPos)) { + if (auto *card = qgraphicsitem_cast(item)) { + if (card->zValue() > cursorItemZ) { + cursorItem = card; + cursorItemZ = card->zValue(); } } } - if ((cursorItem != targetItem) && targetItem) { - targetItem->setBeingPointedAt(false); - } - if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - fullColor = true; - if (cursorItem != startItem) { - cursorItem->setBeingPointedAt(true); + if (cursorItem != targetItem) { + if (targetItem) { + disconnect(positionConnection); + targetItem->setBeingPointedAt(false); } + targetItem = cursorItem; - updatePath(); + fullColor = (cursorItem != nullptr); + + if (cursorItem && cursorItem != startItem) { + cursorItem->setBeingPointedAt(true); + positionConnection = + connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); + } } + + targetItem ? updatePath() : updatePath(endPos); update(); - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseMoveEvent(event); } } -void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) -{ - // do nothing if target is already attached to another card or is not in play - if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { - return; - } - - CardZoneLogic *startZone = startCard->getZone(); - CardZoneLogic *targetZone = targetCard->getZone(); - - // move card onto table first if attaching from some other zone - if (startZone->getName() != ZoneNames::TABLE) { - player->getPlayerActions()->playCardToTable(startCard, false); - } - - Command_AttachCard cmd; - cmd.set_start_zone(ZoneNames::TABLE); - cmd.set_card_id(startCard->getId()); - cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); - cmd.set_target_zone(targetZone->getName().toStdString()); - cmd.set_target_card_id(targetCard->getId()); - - player->getPlayerActions()->sendGameCommand(cmd); -} - void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (!startItem) + if (!startItem) { return; + } // Attaching could move startItem under the current cursor position, causing all children to retarget to it right // before they are processed. Prevent that. - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->setTargetLocked(true); } - if (targetItem && (targetItem != startItem)) { - auto startCard = qgraphicsitem_cast(startItem); - auto targetCard = qgraphicsitem_cast(targetItem); + if (targetItem && targetItem != startItem) { + auto *startCard = qgraphicsitem_cast(startItem); + auto *targetCard = qgraphicsitem_cast(targetItem); if (startCard && targetCard) { attachCards(startCard, targetCard); } } delArrow(); - - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseReleaseEvent(event); } } + +void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) +{ + if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { + return; + } + + // move card onto table first if attaching from some other zone + if (startCard->getZone()->getName() != ZoneNames::TABLE) { + player->getPlayerActions()->playCardToTable(startCard, false); + } + + Command_AttachCard cmd; + cmd.set_start_zone(ZoneNames::TABLE); + cmd.set_card_id(startCard->getId()); + cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId()); + cmd.set_target_zone(targetCard->getZone()->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); + player->getPlayerActions()->sendGameCommand(cmd); +} \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game_graphics/board/arrow_item.h similarity index 64% rename from cockatrice/src/game/board/arrow_item.h rename to cockatrice/src/game_graphics/board/arrow_item.h index cb78ee066..1c306e065 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game_graphics/board/arrow_item.h @@ -1,40 +1,44 @@ -/** - * @file arrow_item.h - * @ingroup GameGraphics - * @brief TODO: Document this. - */ - #ifndef ARROWITEM_H #define ARROWITEM_H +#include "../../game/board/arrow_data.h" +#include "arrow_target.h" + #include +#include +#include class CardItem; class QGraphicsSceneMouseEvent; -class QMenu; -class Player; -class ArrowTarget; +class PlayerLogic; class ArrowItem : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) +signals: + void requestDeletion(int creatorId, int id); + private: QPainterPath path; - QMenu *menu; protected: - Player *player; - int id; - ArrowTarget *startItem, *targetItem; - bool targetLocked; - QColor color; - bool fullColor; + QSharedPointer data; + QPointer startItem; + QPointer targetItem; + bool targetLocked = false; + bool fullColor = true; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public: - ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); - ~ArrowItem() override; + ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem); + + void onTargetDestroyed(); + void delArrow(); + void updatePath(); + void updatePath(const QPointF &endPoint); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; [[nodiscard]] QRectF boundingRect() const override { @@ -44,24 +48,13 @@ public: { return path; } - void updatePath(); - void updatePath(const QPointF &endPoint); - [[nodiscard]] int getId() const { - return id; + return data->id; } - [[nodiscard]] Player *getPlayer() const + [[nodiscard]] int getCreatorId() const { - return player; - } - void setStartItem(ArrowTarget *_item) - { - startItem = _item; - } - void setTargetItem(ArrowTarget *_item) - { - targetItem = _item; + return data->creatorId; } [[nodiscard]] ArrowTarget *getStartItem() const { @@ -75,19 +68,20 @@ public: { targetLocked = _targetLocked; } - void delArrow(); }; class ArrowDragItem : public ArrowItem { Q_OBJECT private: + PlayerLogic *player; int deleteInPhase; QList childArrows; + QMetaObject::Connection positionConnection; public: - ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); - void addChildArrow(ArrowDragItem *childArrow); + ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); + void addChildArrow(ArrowDragItem *child); protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; @@ -98,17 +92,18 @@ class ArrowAttachItem : public ArrowItem { Q_OBJECT private: + PlayerLogic *player; QList childArrows; - + QMetaObject::Connection positionConnection; void attachCards(CardItem *startCard, const CardItem *targetCard); public: explicit ArrowAttachItem(ArrowTarget *_startItem); - void addChildArrow(ArrowAttachItem *childArrow); + void addChildArrow(ArrowAttachItem *child); protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; -#endif // ARROWITEM_H +#endif \ No newline at end of file diff --git a/cockatrice/src/game_graphics/board/arrow_target.cpp b/cockatrice/src/game_graphics/board/arrow_target.cpp new file mode 100644 index 000000000..79b21d921 --- /dev/null +++ b/cockatrice/src/game_graphics/board/arrow_target.cpp @@ -0,0 +1,23 @@ +#include "arrow_target.h" + +#include "../../game/player/player_logic.h" +#include "arrow_item.h" + +ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner) +{ + setFlag(ItemSendsScenePositionChanges); +} + +void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) +{ + beingPointedAt = _beingPointedAt; + update(); +} + +QVariant ArrowTarget::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemScenePositionHasChanged) { + emit scenePositionChanged(); + } + return AbstractGraphicsItem::itemChange(change, value); +} \ No newline at end of file diff --git a/cockatrice/src/game_graphics/board/arrow_target.h b/cockatrice/src/game_graphics/board/arrow_target.h new file mode 100644 index 000000000..bf89c5456 --- /dev/null +++ b/cockatrice/src/game_graphics/board/arrow_target.h @@ -0,0 +1,47 @@ +/** + * @file arrow_target.h + * @ingroup GameGraphics + */ +//! \todo Document this file. + +#ifndef ARROWTARGET_H +#define ARROWTARGET_H + +#include "abstract_graphics_item.h" + +#include + +class PlayerLogic; +class ArrowItem; + +class ArrowTarget : public AbstractGraphicsItem +{ + Q_OBJECT +protected: + PlayerLogic *owner; + +private: + bool beingPointedAt = false; + +signals: + void scenePositionChanged(); + +public: + explicit ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent = nullptr); + ~ArrowTarget() override = default; + + [[nodiscard]] PlayerLogic *getOwner() const + { + return owner; + } + + void setBeingPointedAt(bool _beingPointedAt); + [[nodiscard]] bool getBeingPointedAt() const + { + return beingPointedAt; + } + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; +}; +#endif diff --git a/cockatrice/src/game/board/card_drag_item.cpp b/cockatrice/src/game_graphics/board/card_drag_item.cpp similarity index 90% rename from cockatrice/src/game/board/card_drag_item.cpp rename to cockatrice/src/game_graphics/board/card_drag_item.cpp index 5ae56ccba..49467c5c9 100644 --- a/cockatrice/src/game/board/card_drag_item.cpp +++ b/cockatrice/src/game_graphics/board/card_drag_item.cpp @@ -24,8 +24,9 @@ void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti { AbstractCardDragItem::paint(painter, option, widget); - if (occupied) + if (occupied) { painter->fillPath(shape(), QColor(200, 0, 0, 100)); + } } void CardDragItem::updatePosition(const QPointF &cursorScenePos) @@ -38,16 +39,19 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos) ZoneViewZone *zoneViewZone = 0; for (int i = colliding.size() - 1; i >= 0; i--) { CardZone *temp = qgraphicsitem_cast(colliding.at(i)); - if (!cardZone) + if (!cardZone) { cardZone = temp; - if (!zoneViewZone) + } + if (!zoneViewZone) { zoneViewZone = qobject_cast(temp); + } } CardZone *cursorZone = 0; - if (zoneViewZone) + if (zoneViewZone) { cursorZone = zoneViewZone; - else if (cardZone) + } else if (cardZone) { cursorZone = cardZone; + } // Always update the current zone, even if its null, to cancel the drag // instead of dropping cards into an non-intuitive location. @@ -59,8 +63,9 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos) QPointF newPos = cursorScenePos - hotSpot; if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) + for (int i = 0; i < childDrags.size(); i++) { childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + } setPos(newPos); } @@ -78,23 +83,27 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos) // position. TableZone *tableZone = qobject_cast(cursorZone); QPointF closestGridPoint; - if (tableZone) + if (tableZone) { closestGridPoint = tableZone->closestGridPoint(cursorPosInZone); - else + } else { closestGridPoint = cursorPosInZone - hotSpot; + } QPointF newPos = zonePos + closestGridPoint; if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) + for (int i = 0; i < childDrags.size(); i++) { childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + } setPos(newPos); bool newOccupied = false; TableZone *table = qobject_cast(cursorZone); - if (table) - if (table->getCardFromCoords(closestGridPoint)) + if (table) { + if (table->getCardFromCoords(closestGridPoint)) { newOccupied = true; + } + } if (newOccupied != occupied) { occupied = newOccupied; update(); diff --git a/cockatrice/src/game/board/card_drag_item.h b/cockatrice/src/game_graphics/board/card_drag_item.h similarity index 96% rename from cockatrice/src/game/board/card_drag_item.h rename to cockatrice/src/game_graphics/board/card_drag_item.h index 930c6be6f..74d25ad04 100644 --- a/cockatrice/src/game/board/card_drag_item.h +++ b/cockatrice/src/game_graphics/board/card_drag_item.h @@ -1,8 +1,8 @@ /** * @file card_drag_item.h * @ingroup GameGraphicsCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDDRAGITEM_H #define CARDDRAGITEM_H diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp similarity index 68% rename from cockatrice/src/game/board/card_item.cpp rename to cockatrice/src/game_graphics/board/card_item.cpp index cf3c7db20..cabe988c2 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -1,12 +1,12 @@ #include "card_item.h" #include "../../client/settings/cache_settings.h" +#include "../../game/phase.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/view_zone_logic.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../game_scene.h" -#include "../phase.h" -#include "../player/player.h" -#include "../player/player_actions.h" -#include "../zones/logic/view_zone_logic.h" #include "../zones/table_zone.h" #include "../zones/view_zone.h" #include "arrow_item.h" @@ -20,15 +20,19 @@ #include #include -CardItem::CardItem(Player *_owner, QGraphicsItem *parent, const CardRef &cardRef, int _cardid, CardZoneLogic *_zone) - : AbstractCardItem(parent, cardRef, _owner, _cardid), zone(_zone), attacking(false), destroyOnZoneChange(false), - doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) +CardItem::CardItem(PlayerLogic *_owner, + QGraphicsItem *parent, + const CardRef &cardRef, + int _cardid, + CardZoneLogic *_zone) + : AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr) { owner->addCard(this); connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) { - if (counters.contains(counterId)) + if (state->getCounters().contains(counterId)) { update(); + } }); } @@ -36,7 +40,7 @@ void CardItem::prepareDelete() { if (owner != nullptr) { if (owner->getGame()->getActiveCard() == this) { - owner->getPlayerMenu()->updateCardMenu(nullptr); + emit owner->requestCardMenuUpdate(nullptr); owner->getGame()->setActiveCard(nullptr); } owner = nullptr; @@ -47,23 +51,24 @@ void CardItem::prepareDelete() attachedCards.first()->setAttachedTo(nullptr); } - if (attachedTo != nullptr) { - attachedTo->removeAttachedCard(this); - attachedTo = nullptr; + if (state->getAttachedTo() != nullptr) { + state->getAttachedTo()->removeAttachedCard(this); + state->setAttachedTo(nullptr); } } void CardItem::deleteLater() { prepareDelete(); - if (scene()) + if (scene()) { static_cast(scene())->unregisterAnimationItem(this); + } AbstractCardItem::deleteLater(); } void CardItem::setZone(CardZoneLogic *_zone) { - zone = _zone; + state->setZone(_zone); } void CardItem::retranslateUi() @@ -78,23 +83,23 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, AbstractCardItem::paint(painter, option, widget); int i = 0; - QMapIterator counterIterator(counters); + QMapIterator counterIterator(state->getCounters()); while (counterIterator.hasNext()) { counterIterator.next(); QColor _color = cardCounterSettings.color(counterIterator.key()); - paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter); + paintNumberEllipse(counterIterator.value(), 14, _color, i, state->getCounters().size(), painter); ++i; } QSizeF translatedSize = getTranslatedSize(painter); qreal scaleFactor = translatedSize.width() / boundingRect().width(); - if (!pt.isEmpty()) { + if (!state->getPT().isEmpty()) { painter->save(); transformPainter(painter, translatedSize, tapAngle); - if (!getFaceDown() && pt == exactCard.getInfo().getPowTough()) { + if (!getFaceDown() && state->getPT() == exactCard.getInfo().getPowTough()) { painter->setPen(Qt::white); } else { painter->setPen(QColor(255, 150, 0)); // dark orange @@ -105,11 +110,11 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor, translatedSize.height() - 8 * scaleFactor), - Qt::AlignRight | Qt::AlignBottom, pt); + Qt::AlignRight | Qt::AlignBottom, state->getPT()); painter->restore(); } - if (!annotation.isEmpty()) { + if (!state->getAnnotation().isEmpty()) { painter->save(); transformPainter(painter, translatedSize, tapAngle); @@ -119,7 +124,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), - Qt::AlignCenter | Qt::TextWrapAnywhere, annotation); + Qt::AlignCenter | Qt::TextWrapAnywhere, state->getAnnotation()); painter->restore(); } @@ -127,7 +132,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100))); } - if (doesntUntap) { + if (state->getDoesntUntap()) { painter->save(); painter->setRenderHint(QPainter::Antialiasing, false); @@ -146,69 +151,66 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, void CardItem::setAttacking(bool _attacking) { - attacking = _attacking; + state->setAttacking(_attacking); update(); } void CardItem::setCounter(int _id, int _value) { - if (_value) - counters.insert(_id, _value); - else - counters.remove(_id); + state->setCounter(_id, _value); update(); } void CardItem::setAnnotation(const QString &_annotation) { - annotation = _annotation; + state->setAnnotation(_annotation); update(); } void CardItem::setDoesntUntap(bool _doesntUntap) { - doesntUntap = _doesntUntap; + state->setDoesntUntap(_doesntUntap); update(); } void CardItem::setPT(const QString &_pt) { - pt = _pt; + state->setPT(_pt); update(); } void CardItem::setAttachedTo(CardItem *_attachedTo) { - if (attachedTo != nullptr) { - attachedTo->removeAttachedCard(this); + if (state->getAttachedTo() != nullptr) { + state->getAttachedTo()->removeAttachedCard(this); } gridPoint.setX(-1); - attachedTo = _attachedTo; - if (attachedTo != nullptr) { + state->setAttachedTo(_attachedTo); + if (state->getAttachedTo() != nullptr) { // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // attached cards - if (attachedTo->zone == nullptr) { + if (state->getAttachedTo()->getZone() == nullptr) { deleteLater(); } else { - emit attachedTo->zone->cardAdded(this); - attachedTo->addAttachedCard(this); - if (zone != attachedTo->getZone()) { - attachedTo->getZone()->reorganizeCards(); + emit state->getAttachedTo()->getZone()->cardAdded(this); + state->getAttachedTo()->addAttachedCard(this); + if (state->getZone() != state->getAttachedTo()->getZone()) { + state->getAttachedTo()->getZone()->reorganizeCards(); } } } else { // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // attached cards - if (zone == nullptr) { + if (state->getZone() == nullptr) { deleteLater(); } else { - emit zone->cardAdded(this); + emit state->getZone()->cardAdded(this); } } - if (zone != nullptr) { - zone->reorganizeCards(); + if (state->getZone() != nullptr) { + state->getZone()->reorganizeCards(); } } @@ -217,28 +219,23 @@ void CardItem::setAttachedTo(CardItem *_attachedTo) */ void CardItem::resetState(bool keepAnnotations) { - attacking = false; - counters.clear(); - pt.clear(); - if (!keepAnnotations) { - annotation.clear(); - } - attachedTo = 0; + state->resetState(keepAnnotations); attachedCards.clear(); setTapped(false, false); setDoesntUntap(false); - if (scene()) + if (scene()) { static_cast(scene())->unregisterAnimationItem(this); + } update(); } void CardItem::processCardInfo(const ServerInfo_Card &_info) { - counters.clear(); + state->clearCounters(); const int counterListSize = _info.counter_list_size(); for (int i = 0; i < counterListSize; ++i) { const ServerInfo_CardCounter &counterInfo = _info.counter_list(i); - counters.insert(counterInfo.id(), counterInfo.value()); + state->insertCounter(counterInfo.id(), counterInfo.value()); } setId(_info.id()); @@ -275,11 +272,12 @@ void CardItem::deleteDragItem() void CardItem::drawArrow(const QColor &arrowColor) { - if (owner->getGame()->getPlayerManager()->isSpectator()) + if (owner->getGame()->getPlayerManager()->isSpectator()) { return; + } auto *game = owner->getGame(); - Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + PlayerLogic *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); int phase = 0; // 0 means to not set the phase if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) { int currentPhase = game->getGameState()->getCurrentPhase(); @@ -291,10 +289,12 @@ void CardItem::drawArrow(const QColor &arrowColor) for (const auto &item : scene()->selectedItems()) { CardItem *card = qgraphicsitem_cast(item); - if (card == nullptr || card == this) + if (card == nullptr || card == this) { continue; - if (card->getZone() != zone) + } + if (card->getZone() != state->getZone()) { continue; + } ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase); scene()->addItem(childArrow); @@ -304,8 +304,9 @@ void CardItem::drawArrow(const QColor &arrowColor) void CardItem::drawAttachArrow() { - if (owner->getGame()->getPlayerManager()->isSpectator()) + if (owner->getGame()->getPlayerManager()->isSpectator()) { return; + } auto *arrow = new ArrowAttachItem(this); scene()->addItem(arrow); @@ -313,10 +314,12 @@ void CardItem::drawAttachArrow() for (const auto &item : scene()->selectedItems()) { CardItem *card = qgraphicsitem_cast(item); - if (card == nullptr) + if (card == nullptr) { continue; - if (card->getZone() != zone) + } + if (card->getZone() != state->getZone()) { continue; + } ArrowAttachItem *childArrow = new ArrowAttachItem(card); scene()->addItem(childArrow); @@ -328,27 +331,32 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons().testFlag(Qt::RightButton)) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() < - 2 * QApplication::startDragDistance()) + 2 * QApplication::startDragDistance()) { return; + } QColor arrowColor = Qt::red; - if (event->modifiers().testFlag(Qt::ControlModifier)) + if (event->modifiers().testFlag(Qt::ControlModifier)) { arrowColor = Qt::yellow; - else if (event->modifiers().testFlag(Qt::AltModifier)) + } else if (event->modifiers().testFlag(Qt::AltModifier)) { arrowColor = Qt::blue; - else if (event->modifiers().testFlag(Qt::ShiftModifier)) + } else if (event->modifiers().testFlag(Qt::ShiftModifier)) { arrowColor = Qt::green; + } drawArrow(arrowColor); } else if (event->buttons().testFlag(Qt::LeftButton)) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < - 2 * QApplication::startDragDistance()) + 2 * QApplication::startDragDistance()) { return; - if (const ZoneViewZoneLogic *view = qobject_cast(zone)) { - if (view->getRevealZone() && !view->getWriteableRevealZone()) + } + if (const ZoneViewZoneLogic *view = qobject_cast(state->getZone())) { + if (view->getRevealZone() && !view->getWriteableRevealZone()) { return; - } else if (!owner->getPlayerInfo()->getLocalOrJudge()) + } + } else if (!owner->getPlayerInfo()->getLocalOrJudge()) { return; + } bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); @@ -360,14 +368,16 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) int childIndex = 0; for (const auto &item : scene()->selectedItems()) { CardItem *card = static_cast(item); - if ((card == this) || (card->getZone() != zone)) + if ((card == this) || (card->getZone() != state->getZone())) { continue; + } ++childIndex; QPointF childPos; - if (zone->getHasCardAttr()) + if (state->getZone()->getHasCardAttr()) { childPos = card->pos() - pos(); - else + } else { childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0); + } CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem); drag->setPos(dragItem->pos() + childPos); @@ -380,22 +390,57 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void CardItem::playCard(bool faceDown) { // Do nothing if the card belongs to another player - if (!owner->getPlayerInfo()->getLocalOrJudge()) + if (!owner->getPlayerInfo()->getLocalOrJudge()) { return; + } - TableZoneLogic *tz = qobject_cast(zone); - if (tz) + TableZoneLogic *tz = qobject_cast(state->getZone()); + if (tz) { emit tz->toggleTapped(); - else { + } else { if (SettingsCache::instance().getClickPlaysAllSelected()) { - faceDown ? zone->getPlayer()->getPlayerActions()->actPlayFacedown() - : zone->getPlayer()->getPlayerActions()->actPlay(); + if (faceDown) { + emit playSelectedFaceDown(this); + } else { + emit playSelected(this); + } } else { - zone->getPlayer()->getPlayerActions()->playCard(this, faceDown); + state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown); } } } +QVariantList CardItem::parsePT(const QString &pt) +{ + QVariantList ptList = QVariantList(); + if (!pt.isEmpty()) { + int sep = pt.indexOf('/'); + if (sep == 0) { + ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string + } else { + int start = 0; + for (;;) { + QString item = pt.mid(start, sep - start); + if (item.isEmpty()) { + ptList.append(QVariant(QString())); + } else if (item[0] == '+') { + ptList.append(QVariant(item.mid(1).toInt())); // add as int + } else if (item[0] == '-') { + ptList.append(QVariant(item.toInt())); // add as int + } else { + ptList.append(QVariant(item)); // add as qstring + } + if (sep == -1) { + break; + } + start = sep + 1; + sep = pt.indexOf('/', start); + } + } + } + return ptList; +} + /** * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone * is nullptr. @@ -416,11 +461,11 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone) */ void CardItem::handleClickedToPlay(bool shiftHeld) { - if (isUnwritableRevealZone(zone)) { + if (isUnwritableRevealZone(state->getZone())) { if (SettingsCache::instance().getClickPlaysAllSelected()) { - zone->getPlayer()->getPlayerActions()->actHide(); + emit hideSelected(this); } else { - zone->removeCard(this); + state->getZone()->removeCard(this); } } else { playCard(shiftHeld); @@ -429,21 +474,15 @@ void CardItem::handleClickedToPlay(bool shiftHeld) void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::RightButton) { - - if (owner != nullptr) { - owner->getGame()->setActiveCard(this); - if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) { - cardMenu->popup(event->screenPos()); - return; - } - } - } else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && - (!SettingsCache::instance().getDoubleClickToPlay())) { + if (event->button() == Qt::RightButton && owner != nullptr) { + emit rightClicked(this, event->screenPos()); + return; + } + if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && + (!SettingsCache::instance().getDoubleClickToPlay())) { handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } - - if (owner != nullptr) { // cards without owner will be deleted + if (owner != nullptr) { setCursor(Qt::OpenHandCursor); } AbstractCardItem::mouseReleaseEvent(event); @@ -460,13 +499,11 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) bool CardItem::animationEvent() { - if (owner == nullptr) { - return false; - } int rotation = ROTATION_DEGREES_PER_FRAME; bool animationIncomplete = true; - if (!tapped) + if (!tapped) { rotation *= -1; + } tapAngle += rotation; if (tapped && (tapAngle > 90)) { @@ -491,14 +528,14 @@ bool CardItem::animationEvent() QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) { if ((change == ItemSelectedHasChanged) && owner != nullptr) { - if (value == true) { - owner->getGame()->setActiveCard(this); - owner->getPlayerMenu()->updateCardMenu(this); - } else if (owner->getGameScene()->selectedItems().isEmpty()) { + bool selected = value.toBool(); - owner->getGame()->setActiveCard(nullptr); - owner->getPlayerMenu()->updateCardMenu(nullptr); + if (selected) { + owner->getGame()->setActiveCard(this); } + + emit selectionChanged(this, selected); } + return AbstractCardItem::itemChange(change, value); -} +} \ No newline at end of file diff --git a/cockatrice/src/game/board/card_item.h b/cockatrice/src/game_graphics/board/card_item.h similarity index 64% rename from cockatrice/src/game/board/card_item.h rename to cockatrice/src/game_graphics/board/card_item.h index da2097a2c..8efcd085d 100644 --- a/cockatrice/src/game/board/card_item.h +++ b/cockatrice/src/game_graphics/board/card_item.h @@ -1,42 +1,37 @@ /** * @file card_item.h * @ingroup GameGraphicsCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDITEM_H #define CARDITEM_H -#include "../zones/logic/card_zone_logic.h" +#include "../../game/board/card_state.h" +#include "../../game/zones/card_zone_logic.h" #include "abstract_card_item.h" #include +#include class CardDatabase; class CardDragItem; class CardZone; class ServerInfo_Card; -class Player; +class PlayerLogic; class QAction; class QColor; -const int MAX_COUNTERS_ON_CARD = 999; const int ROTATION_DEGREES_PER_FRAME = 10; class CardItem : public AbstractCardItem { Q_OBJECT private: - CardZoneLogic *zone; - bool attacking; - QMap counters; - QString annotation; - QString pt; - bool destroyOnZoneChange; - bool doesntUntap; + CardState *state; + QPoint gridPoint; CardDragItem *dragItem; - CardItem *attachedTo; QList attachedCards; void prepareDelete(); @@ -53,16 +48,20 @@ public: { return Type; } - explicit CardItem(Player *_owner, + explicit CardItem(PlayerLogic *_owner, QGraphicsItem *parent = nullptr, const CardRef &cardRef = {}, int _cardid = -1, CardZoneLogic *_zone = nullptr); void retranslateUi(); + [[nodiscard]] CardState *getState() const + { + return state; + } [[nodiscard]] CardZoneLogic *getZone() const { - return zone; + return state->getZone(); } void setZone(CardZoneLogic *_zone); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; @@ -78,50 +77,50 @@ public: { return gridPoint; } - [[nodiscard]] Player *getOwner() const + [[nodiscard]] PlayerLogic *getOwner() const { return owner; } - void setOwner(Player *_owner) + void setOwner(PlayerLogic *_owner) { owner = _owner; } [[nodiscard]] bool getAttacking() const { - return attacking; + return state->getAttacking(); } void setAttacking(bool _attacking); [[nodiscard]] const QMap &getCounters() const { - return counters; + return state->getCounters(); } void setCounter(int _id, int _value); [[nodiscard]] QString getAnnotation() const { - return annotation; + return state->getAnnotation(); } void setAnnotation(const QString &_annotation); [[nodiscard]] bool getDoesntUntap() const { - return doesntUntap; + return state->getDoesntUntap(); } void setDoesntUntap(bool _doesntUntap); [[nodiscard]] QString getPT() const { - return pt; + return state->getPT(); } void setPT(const QString &_pt); [[nodiscard]] bool getDestroyOnZoneChange() const { - return destroyOnZoneChange; + return state->getDestroyOnZoneChange(); } void setDestroyOnZoneChange(bool _destroy) { - destroyOnZoneChange = _destroy; + state->setDestroyOnZoneChange(_destroy); } [[nodiscard]] CardItem *getAttachedTo() const { - return attachedTo; + return state->getAttachedTo(); } void setAttachedTo(CardItem *_attachedTo); void addAttachedCard(CardItem *card) @@ -146,6 +145,26 @@ public: void drawAttachArrow(); void playCard(bool faceDown); + /** + * @brief Parses a string representing a p/t in order to extract the values from it. + * + * If the string contains '/', the string will be split at the '/' and each side will be parsed separately, + * which means the result list will have two elements. + * + * If '/' is not found, then the entire string is parsed together, which means the result list will + * have a single element. + * + * If either side of the split is empty, there will also only be a single element in the result list. + * + * This function will attempt to parse each substring as an int first, handling plus and minus prefixes. + * If successful, it will put the parsed value into the QVariant as an int. + * If failed, it will just put the substring into the QVariant as a QString. + * + * @param pt The p/t string + * @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString + */ + static QVariantList parsePT(const QString &pt); + protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/cockatrice/src/game/board/counter_general.cpp b/cockatrice/src/game_graphics/board/counter_general.cpp similarity index 65% rename from cockatrice/src/game/board/counter_general.cpp rename to cockatrice/src/game_graphics/board/counter_general.cpp index d68486a1b..379c6f837 100644 --- a/cockatrice/src/game/board/counter_general.cpp +++ b/cockatrice/src/game_graphics/board/counter_general.cpp @@ -1,19 +1,12 @@ #include "counter_general.h" -#include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/pixel_map_generator.h" +#include "abstract_graphics_item.h" #include -GeneralCounter::GeneralCounter(Player *_player, - int _id, - const QString &_name, - const QColor &_color, - int _radius, - int _value, - bool useNameForShortcut, - QGraphicsItem *parent) - : AbstractCounter(_player, _id, _name, true, _value, useNameForShortcut, parent), color(_color), radius(_radius) +GeneralCounter::GeneralCounter(CounterState *state, PlayerLogic *player, bool useNameForShortcut, QGraphicsItem *parent) + : AbstractCounter(state, player, true, useNameForShortcut, parent) { setCacheMode(DeviceCoordinateCache); } diff --git a/cockatrice/src/game/board/counter_general.h b/cockatrice/src/game_graphics/board/counter_general.h similarity index 62% rename from cockatrice/src/game/board/counter_general.h rename to cockatrice/src/game_graphics/board/counter_general.h index 3db1d7bb4..0a2e882ce 100644 --- a/cockatrice/src/game/board/counter_general.h +++ b/cockatrice/src/game_graphics/board/counter_general.h @@ -1,8 +1,8 @@ /** * @file counter_general.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COUNTER_GENERAL_H #define COUNTER_GENERAL_H @@ -12,17 +12,10 @@ class GeneralCounter : public AbstractCounter { Q_OBJECT -private: - QColor color; - int radius; public: - GeneralCounter(Player *_player, - int _id, - const QString &_name, - const QColor &_color, - int _radius, - int _value, + GeneralCounter(CounterState *state, + PlayerLogic *player, bool useNameForShortcut = false, QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; diff --git a/cockatrice/src/game_graphics/board/graphics_item_type.h b/cockatrice/src/game_graphics/board/graphics_item_type.h index c48ae4ed6..7eac132b0 100644 --- a/cockatrice/src/game_graphics/board/graphics_item_type.h +++ b/cockatrice/src/game_graphics/board/graphics_item_type.h @@ -1,8 +1,8 @@ /** * @file graphics_item_type.h * @ingroup GameGraphics - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_GRAPHICS_ITEM_TYPE_H #define COCKATRICE_GRAPHICS_ITEM_TYPE_H diff --git a/cockatrice/src/game/board/translate_counter_name.cpp b/cockatrice/src/game_graphics/board/translate_counter_name.cpp similarity index 100% rename from cockatrice/src/game/board/translate_counter_name.cpp rename to cockatrice/src/game_graphics/board/translate_counter_name.cpp diff --git a/cockatrice/src/game/board/translate_counter_name.h b/cockatrice/src/game_graphics/board/translate_counter_name.h similarity index 94% rename from cockatrice/src/game/board/translate_counter_name.h rename to cockatrice/src/game_graphics/board/translate_counter_name.h index fdb277c11..ba3a94fa5 100644 --- a/cockatrice/src/game/board/translate_counter_name.h +++ b/cockatrice/src/game_graphics/board/translate_counter_name.h @@ -1,8 +1,8 @@ /** * @file translate_counter_name.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TRANSLATECOUNTERNAME_H #define TRANSLATECOUNTERNAME_H diff --git a/cockatrice/src/game/card_dimensions.h b/cockatrice/src/game_graphics/card_dimensions.h similarity index 76% rename from cockatrice/src/game/card_dimensions.h rename to cockatrice/src/game_graphics/card_dimensions.h index 255d0bc04..c59a01b48 100644 --- a/cockatrice/src/game/card_dimensions.h +++ b/cockatrice/src/game_graphics/card_dimensions.h @@ -12,16 +12,16 @@ */ namespace CardDimensions { -/// Card width in pixels +/** @brief Card width in pixels. */ constexpr int WIDTH = 72; -/// Card height in pixels +/** @brief Card height in pixels. */ constexpr int HEIGHT = 102; -/// Pre-converted for floating-point contexts (Z-value calculations) +/** @brief Pre-converted for floating-point contexts (Z-value calculations). */ constexpr qreal WIDTH_F = static_cast(WIDTH); constexpr qreal HEIGHT_F = static_cast(HEIGHT); -/// Half-dimensions for centering and rotation transforms +/** @brief Half-dimensions for centering and rotation transforms. */ constexpr qreal WIDTH_HALF_F = WIDTH_F / 2; constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2; } // namespace CardDimensions diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp similarity index 92% rename from cockatrice/src/game/deckview/deck_view.cpp rename to cockatrice/src/game_graphics/deckview/deck_view.cpp index 620dfaa5f..ced02c8db 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -24,17 +24,21 @@ void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos) QList colliding = scene()->items(cursorScenePos); DeckViewCardContainer *cursorZone = 0; - for (int i = colliding.size() - 1; i >= 0; i--) - if ((cursorZone = qgraphicsitem_cast(colliding.at(i)))) + for (int i = colliding.size() - 1; i >= 0; i--) { + if ((cursorZone = qgraphicsitem_cast(colliding.at(i)))) { break; - if (!cursorZone) + } + } + if (!cursorZone) { return; + } currentZone = cursorZone; QPointF newPos = cursorScenePos; if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) + for (int i = 0; i < childDrags.size(); i++) { childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + } setPos(newPos); } } @@ -104,11 +108,13 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < - 2 * QApplication::startDragDistance()) + 2 * QApplication::startDragDistance()) { return; + } - if (static_cast(scene())->getLocked()) + if (static_cast(scene())->getLocked()) { return; + } delete dragItem; dragItem = new DeckViewCardDragItem(this, event->pos()); @@ -120,8 +126,9 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) int j = 0; for (int i = 0; i < sel.size(); i++) { auto *c = static_cast(sel.at(i)); - if (c == this) + if (c == this) { continue; + } ++j; auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0); auto *drag = new DeckViewCardDragItem(c, childPos, dragItem); @@ -133,8 +140,9 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void DeckView::mouseDoubleClickEvent(QMouseEvent *event) { - if (static_cast(scene())->getLocked()) + if (static_cast(scene())->getLocked()) { return; + } if (event->button() == Qt::LeftButton) { QList result; @@ -147,12 +155,13 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event) m.set_card_name(c->getName().toStdString()); m.set_start_zone(zone->getName().toStdString()); - if (zone->getName() == DECK_ZONE_MAIN) + if (zone->getName() == DECK_ZONE_MAIN) { m.set_target_zone(DECK_ZONE_SIDE); - else if (zone->getName() == DECK_ZONE_SIDE) + } else if (zone->getName() == DECK_ZONE_SIDE) { m.set_target_zone(DECK_ZONE_MAIN); - else // Trying to move from another zone + } else { // Trying to move from another zone m.set_target_zone(zone->getName().toStdString()); + } result.append(m); } @@ -232,8 +241,9 @@ QList> DeckViewCardContainer::getRowsAndCols() const { QList> result; QList cardTypeList = cardsByType.uniqueKeys(); - for (int i = 0; i < cardTypeList.size(); ++i) + for (int i = 0; i < cardTypeList.size(); ++i) { result.append(QPair(1, cardsByType.count(cardTypeList[i]))); + } return result; } @@ -262,8 +272,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList> // Calculate space needed for cards for (int i = 0; i < rowsAndCols.size(); ++i) { totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY; - if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) + if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) { totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second; + } } return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY); @@ -271,8 +282,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList> bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2) { - if (c1 && c2) + if (c1 && c2) { return c1->getName() < c2->getName(); + } return false; } @@ -322,15 +334,17 @@ DeckViewScene::~DeckViewScene() void DeckViewScene::clearContents() { QMapIterator i(cardContainers); - while (i.hasNext()) + while (i.hasNext()) { delete i.next().value(); + } cardContainers.clear(); } void DeckViewScene::setDeck(const DeckList &_deck) { - if (deck) + if (deck) { delete deck; + } deck = new DeckList(_deck.writeToString_Native()); rebuildTree(); @@ -342,8 +356,9 @@ void DeckViewScene::rebuildTree() { clearContents(); - if (!deck) + if (!deck) { return; + } for (auto *currentZone : deck->getZoneNodes()) { DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); @@ -355,8 +370,9 @@ void DeckViewScene::rebuildTree() for (int j = 0; j < currentZone->size(); j++) { auto *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) + if (!currentCard) { continue; + } for (int k = 0; k < currentCard->getNumber(); ++k) { auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName()); @@ -373,18 +389,21 @@ void DeckViewScene::applySideboardPlan(const QList &plan) const MoveCard_ToZone &m = plan[i]; DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone())); DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone())); - if (!start || !target) + if (!start || !target) { continue; + } DeckViewCard *card = 0; const QList &cardList = start->getCards(); - for (int j = 0; j < cardList.size(); ++j) + for (int j = 0; j < cardList.size(); ++j) { if (cardList[j]->getName() == QString::fromStdString(m.card_name())) { card = cardList[j]; break; } - if (!card) + } + if (!card) { continue; + } start->removeCard(card); target->addCard(card); @@ -405,8 +424,9 @@ void DeckViewScene::rearrangeItems() rowsAndColsList.append(rowsAndCols); cardCountList.append(QList()); - for (int j = 0; j < rowsAndCols.size(); ++j) + for (int j = 0; j < rowsAndCols.size(); ++j) { cardCountList[i].append(rowsAndCols[j].second); + } } qreal totalHeight, totalWidth; @@ -417,23 +437,27 @@ void DeckViewScene::rearrangeItems() for (int i = 0; i < contList.size(); ++i) { QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]); totalHeight += contSize.height() + spacing; - if (contSize.width() > totalWidth) + if (contSize.width() > totalWidth) { totalWidth = contSize.width(); + } } // We're done when the aspect ratio shifts from too high to too low. - if (totalWidth / totalHeight <= optimalAspectRatio) + if (totalWidth / totalHeight <= optimalAspectRatio) { break; + } // Find category with highest column count int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0; - for (int i = 0; i < rowsAndColsList.size(); ++i) - for (int j = 0; j < rowsAndColsList[i].size(); ++j) + for (int i = 0; i < rowsAndColsList.size(); ++i) { + for (int j = 0; j < rowsAndColsList[i].size(); ++j) { if (rowsAndColsList[i][j].second > maxCols) { maxIndex1 = i; maxIndex2 = j; maxCols = rowsAndColsList[i][j].second; } + } + } // Add row to category const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first; @@ -451,8 +475,9 @@ void DeckViewScene::rearrangeItems() } totalWidth = totalHeight * optimalAspectRatio; - for (int i = 0; i < contList.size(); ++i) + for (int i = 0; i < contList.size(); ++i) { contList[i]->setWidth(totalWidth); + } setSceneRect(QRectF(0, 0, totalWidth, totalHeight)); } @@ -470,7 +495,7 @@ QList DeckViewScene::getSideboardPlan() const while (containerIterator.hasNext()) { DeckViewCardContainer *cont = containerIterator.next().value(); const QList cardList = cont->getCards(); - for (int i = 0; i < cardList.size(); ++i) + for (int i = 0; i < cardList.size(); ++i) { if (cardList[i]->getOriginZone() != cont->getName()) { MoveCard_ToZone m; m.set_card_name(cardList[i]->getName().toStdString()); @@ -478,6 +503,7 @@ QList DeckViewScene::getSideboardPlan() const m.set_target_zone(cont->getName().toStdString()); result.append(m); } + } } return result; } diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game_graphics/deckview/deck_view.h similarity index 99% rename from cockatrice/src/game/deckview/deck_view.h rename to cockatrice/src/game_graphics/deckview/deck_view.h index 5abc558bd..f996fd4da 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game_graphics/deckview/deck_view.h @@ -1,8 +1,8 @@ /** * @file deck_view.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECKVIEW_H #define DECKVIEW_H diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp similarity index 99% rename from cockatrice/src/game/deckview/deck_view_container.cpp rename to cockatrice/src/game_graphics/deckview/deck_view_container.cpp index 44b2be6d1..cbd6c2bad 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp @@ -251,8 +251,9 @@ void DeckViewContainer::unloadDeck() void DeckViewContainer::loadLocalDeck() { DlgLoadDeck dialog(this); - if (!dialog.exec()) + if (!dialog.exec()) { return; + } loadDeckFromFile(dialog.selectedFiles().at(0)); } @@ -364,8 +365,9 @@ void DeckViewContainer::sideboardPlanChanged() { Command_SetSideboardPlan cmd; const QList &newPlan = deckView->getSideboardPlan(); - for (const auto &i : newPlan) + for (const auto &i : newPlan) { cmd.add_move_list()->CopyFrom(i); + } parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId); } @@ -404,8 +406,9 @@ void DeckViewContainer::setSideboardLocked(bool locked) { sideboardLockButton->setState(!locked); deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState()); - if (locked) + if (locked) { deckView->resetSideboardPlan(); + } } void DeckViewContainer::setDeck(const DeckList &deck) diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game_graphics/deckview/deck_view_container.h similarity index 98% rename from cockatrice/src/game/deckview/deck_view_container.h rename to cockatrice/src/game_graphics/deckview/deck_view_container.h index 6d685cd79..ec024bace 100644 --- a/cockatrice/src/game/deckview/deck_view_container.h +++ b/cockatrice/src/game_graphics/deckview/deck_view_container.h @@ -1,8 +1,8 @@ /** * @file deck_view_container.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_VIEW_CONTAINER_H #define DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/deckview/tabbed_deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp similarity index 100% rename from cockatrice/src/game/deckview/tabbed_deck_view_container.cpp rename to cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.cpp diff --git a/cockatrice/src/game/deckview/tabbed_deck_view_container.h b/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.h similarity index 95% rename from cockatrice/src/game/deckview/tabbed_deck_view_container.h rename to cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.h index c34eef1ef..7cfa8c9aa 100644 --- a/cockatrice/src/game/deckview/tabbed_deck_view_container.h +++ b/cockatrice/src/game_graphics/deckview/tabbed_deck_view_container.h @@ -1,8 +1,8 @@ /** * @file tabbed_deck_view_container.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TABBED_DECK_VIEW_CONTAINER_H #define TABBED_DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp similarity index 98% rename from cockatrice/src/game/dialogs/dlg_create_token.cpp rename to cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp index df264f065..11c24b72e 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game_graphics/dialogs/dlg_create_token.cpp @@ -101,8 +101,9 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa chooseTokenView->resizeColumnToContents(0); chooseTokenView->setWordWrap(true); - if (!deckHeaderState.isNull()) + if (!deckHeaderState.isNull()) { chooseTokenView->header()->restoreState(deckHeaderState); + } chooseTokenView->header()->setStretchLastSection(false); chooseTokenView->header()->hideSection(1); // Sets @@ -185,8 +186,9 @@ void DlgCreateToken::tokenSelectionChanged(const QModelIndex ¤t, const QMo const QChar cardColor = cardInfo->getColorChar(); colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString)); ptEdit->setText(cardInfo->getPowTough()); - if (SettingsCache::instance().getAnnotateTokens()) + if (SettingsCache::instance().getAnnotateTokens()) { annotationEdit->setText(cardInfo->getText()); + } } else { nameEdit->setText(""); colorEdit->setCurrentIndex(colorEdit->findData(QString(), Qt::UserRole, Qt::MatchFixedString)); diff --git a/cockatrice/src/game/dialogs/dlg_create_token.h b/cockatrice/src/game_graphics/dialogs/dlg_create_token.h similarity index 98% rename from cockatrice/src/game/dialogs/dlg_create_token.h rename to cockatrice/src/game_graphics/dialogs/dlg_create_token.h index 9a18f1a57..281e161fc 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.h +++ b/cockatrice/src/game_graphics/dialogs/dlg_create_token.h @@ -1,8 +1,8 @@ /** * @file dlg_create_token.h * @ingroup GameDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_CREATETOKEN_H #define DLG_CREATETOKEN_H diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/game_graphics/dialogs/dlg_move_top_cards_until.cpp similarity index 100% rename from cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp rename to cockatrice/src/game_graphics/dialogs/dlg_move_top_cards_until.cpp diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/game_graphics/dialogs/dlg_move_top_cards_until.h similarity index 97% rename from cockatrice/src/game/dialogs/dlg_move_top_cards_until.h rename to cockatrice/src/game_graphics/dialogs/dlg_move_top_cards_until.h index 20ba11c5c..ac9d41a94 100644 --- a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h +++ b/cockatrice/src/game_graphics/dialogs/dlg_move_top_cards_until.h @@ -1,8 +1,8 @@ /** * @file dlg_move_top_cards_until.h * @ingroup GameDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_MOVE_TOP_CARDS_UNTIL_H #define DLG_MOVE_TOP_CARDS_UNTIL_H diff --git a/cockatrice/src/game/dialogs/dlg_roll_dice.cpp b/cockatrice/src/game_graphics/dialogs/dlg_roll_dice.cpp similarity index 100% rename from cockatrice/src/game/dialogs/dlg_roll_dice.cpp rename to cockatrice/src/game_graphics/dialogs/dlg_roll_dice.cpp diff --git a/cockatrice/src/game/dialogs/dlg_roll_dice.h b/cockatrice/src/game_graphics/dialogs/dlg_roll_dice.h similarity index 95% rename from cockatrice/src/game/dialogs/dlg_roll_dice.h rename to cockatrice/src/game_graphics/dialogs/dlg_roll_dice.h index 69edd0757..15f7dc1ba 100644 --- a/cockatrice/src/game/dialogs/dlg_roll_dice.h +++ b/cockatrice/src/game_graphics/dialogs/dlg_roll_dice.h @@ -1,8 +1,8 @@ /** * @file dlg_roll_dice.h * @ingroup GameDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_ROLL_DICE_H #define DLG_ROLL_DICE_H diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp similarity index 63% rename from cockatrice/src/game/game_scene.cpp rename to cockatrice/src/game_graphics/game_scene.cpp index 034ff6947..b9816a602 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -1,10 +1,15 @@ #include "game_scene.h" #include "../client/settings/cache_settings.h" +#include "../game/abstract_game.h" +#include "../game/player/player_actions.h" +#include "../game/player/player_logic.h" +#include "../game_graphics/player/player_graphics_item.h" #include "board/card_item.h" #include "phases_toolbar.h" -#include "player/player.h" +#include "player/menu/player_menu.h" #include "player/player_graphics_item.h" +#include "zones/select_zone.h" #include "zones/view_zone.h" #include "zones/view_zone_widget.h" @@ -54,8 +59,95 @@ GameScene::~GameScene() */ void GameScene::retranslateUi() { - for (ZoneViewWidget *view : zoneViews) + for (ZoneViewWidget *view : zoneViews) { view->retranslateUi(); + } +} + +QList GameScene::selectedCards() const +{ + QList selectedCards; + for (auto item : selectedItems()) { + if (auto card = qgraphicsitem_cast(item)) { + selectedCards.append(card); + } + } + + return selectedCards; +} + +void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool selected) +{ + CardItem *card = qobject_cast(abstractCard); + if (!card || !card->getOwner()) { + return; + } + + auto *owner = card->getOwner(); + + if (selected) { + owner->requestCardMenuUpdate(card); + return; + } + + if (selectedItems().isEmpty()) { + owner->getGame()->setActiveCard(nullptr); + owner->requestCardMenuUpdate(nullptr); + } +} + +void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screenPos) +{ + auto *card = qobject_cast(abstractCard); + if (!card) { + return; + } + if (!card->getOwner()) { + return; + } + auto *view = playerViews.value(card->getOwner()->getPlayerInfo()->getId()); + if (!view) { + return; + } + + card->getOwner()->getGame()->setActiveCard(card); + + if (auto *menu = view->getPlayerMenu()->updateCardMenu(card)) { + menu->popup(screenPos); + } +} + +void GameScene::playSelected(AbstractCardItem *card) +{ + if (!card) { + return; + } + if (!card->getOwner()) { + return; + } + card->getOwner()->getPlayerActions()->actPlay(selectedCards()); +} + +void GameScene::playSelectedFaceDown(AbstractCardItem *card) +{ + if (!card) { + return; + } + if (!card->getOwner()) { + return; + } + card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCards()); +} + +void GameScene::hideSelected(AbstractCardItem *card) +{ + if (!card) { + return; + } + if (!card->getOwner()) { + return; + } + card->getOwner()->getPlayerActions()->actHide(selectedCards()); } /** @@ -64,13 +156,34 @@ void GameScene::retranslateUi() * * Connects to the player's sizeChanged signal to recompute layout on resize. */ -void GameScene::addPlayer(Player *player) +void GameScene::addPlayer(PlayerLogic *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName(); - players << player->getGraphicsItem(); - addItem(player->getGraphicsItem()); - connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); + auto *view = new PlayerGraphicsItem(player); + + playerViews.insert(player->getPlayerInfo()->getId(), view); + addItem(view); + connect(view, &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); + + connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) { + if (conceded) { + clearArrowsForPlayer(id); + } + rearrange(); + }); + + connect(player, &PlayerLogic::requestZoneViewToggle, this, &GameScene::toggleZoneView); + connect(player, &PlayerLogic::requestRevealedZoneView, this, &GameScene::addRevealedZoneView); + connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow); + connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow); + connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion); + connect(player, &PlayerLogic::arrowsClearedLocally, this, + [this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); }); + + connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged); + + rearrange(); } /** @@ -79,17 +192,20 @@ void GameScene::addPlayer(Player *player) * * Closes any zone views associated with the player and recomputes layout. */ -void GameScene::removePlayer(Player *player) +void GameScene::removePlayer(PlayerLogic *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::removePlayer name=" << player->getPlayerInfo()->getName(); + clearArrowsForPlayer(player->getPlayerInfo()->getId()); + for (ZoneViewWidget *zone : zoneViews) { if (zone->getPlayer() == player) { zone->close(); } } - players.removeOne(player->getGraphicsItem()); - removeItem(player->getGraphicsItem()); + auto *view = playerViews.take(player->getPlayerInfo()->getId()); + removeItem(view); + view->deleteLater(); rearrange(); } @@ -164,14 +280,14 @@ void GameScene::processViewSizeChange(const QSize &newSize) * * Used to determine rotation and layout order. */ -QList GameScene::collectActivePlayers(int &firstPlayerIndex) const +QList GameScene::collectActivePlayers(int &firstPlayerIndex) const { - QList activePlayers; + QList activePlayers; firstPlayerIndex = 0; bool firstPlayerFound = false; - for (auto *pgItem : players) { - Player *p = pgItem->getPlayer(); + for (auto *pgItem : playerViews.values()) { + PlayerLogic *p = pgItem->getLogic(); if (p && !p->getConceded()) { activePlayers.append(p); if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) { @@ -191,15 +307,17 @@ QList GameScene::collectActivePlayers(int &firstPlayerIndex) const * * Applies rotation offset and ensures the list wraps correctly. */ -QList GameScene::rotatePlayers(const QList &activePlayers, int firstPlayerIndex) const +QList GameScene::rotatePlayers(const QList &activePlayers, int firstPlayerIndex) const { - QList rotated = activePlayers; + QList rotated = activePlayers; if (!rotated.isEmpty()) { int totalRotation = firstPlayerIndex + playerRotation; - while (totalRotation < 0) + while (totalRotation < 0) { totalRotation += rotated.size(); - for (int i = 0; i < totalRotation; ++i) + } + for (int i = 0; i < totalRotation; ++i) { rotated.append(rotated.takeFirst()); + } } return rotated; } @@ -222,7 +340,7 @@ int GameScene::determineColumnCount(int playerCount) * - Position players in columns with spacing. * - Mirror graphics for visual balance. */ -QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns) +QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns) { playersByColumn.clear(); @@ -230,7 +348,7 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &players qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing; QList columnWidth; - QListIterator playersIter(playersPlaying); + QListIterator playersIter(playersPlaying); for (int col = 0; col < columns; ++col) { playersByColumn.append(QList()); columnWidth.append(0); @@ -238,13 +356,14 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &players int rowsInColumn = rows - (playersPlaying.size() % columns) * col; // Adjust rows for uneven columns for (int j = 0; j < rowsInColumn; ++j) { - Player *player = playersIter.next(); - if (col == 0) - playersByColumn[col].prepend(player->getGraphicsItem()); - else - playersByColumn[col].append(player->getGraphicsItem()); + PlayerLogic *player = playersIter.next(); + if (col == 0) { + playersByColumn[col].prepend(playerViews.value(player->getPlayerInfo()->getId())); + } else { + playersByColumn[col].append(playerViews.value(player->getPlayerInfo()->getId())); + } - auto *pgItem = player->getGraphicsItem(); + auto *pgItem = playerViews.value(player->getPlayerInfo()->getId()); thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing; columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width()); } @@ -281,8 +400,9 @@ QList GameScene::calculateMinWidthByColumn() const QList minWidthByColumn; for (const auto &col : playersByColumn) { qreal maxWidth = 0; - for (PlayerGraphicsItem *player : col) + for (PlayerGraphicsItem *player : col) { maxWidth = std::max(maxWidth, player->getMinimumWidth()); + } minWidthByColumn.append(maxWidth); } return minWidthByColumn; @@ -330,6 +450,89 @@ void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qr } } +void GameScene::addArrow(QSharedPointer data) +{ + auto *startView = playerViews.value(data->startPlayerId); + auto *targetView = playerViews.value(data->targetPlayerId); + if (!startView || !targetView) { + return; + } + + PlayerLogic *startLogic = startView->getLogic(); + auto *startZone = startLogic->getZones().value(data->startZone); + if (!startZone) { + return; + } + + CardItem *startCard = startZone->getCard(data->startCardId); + if (!startCard) { + return; + } + + ArrowTarget *targetItem = nullptr; + if (data->isPlayerTargeted()) { + targetItem = targetView->getPlayerTarget(); + } else { + auto *zone = targetView->getLogic()->getZones().value(data->targetZone); + if (zone) { + targetItem = zone->getCard(data->targetCardId); + } + } + if (!targetItem) { + return; + } + + auto *arrow = new ArrowItem(data, startCard, targetItem); + addItem(arrow); + arrowRegistry.insert(data, arrow); + connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion); +} + +void GameScene::deleteArrow(int playerId, int arrowId) +{ + if (auto *arrow = arrowRegistry.take(playerId, arrowId)) { + arrow->delArrow(); + } +} + +void GameScene::requestArrowDeletion(int playerId, int arrowId) +{ + if (arrowRegistry.contains(playerId, arrowId)) { + emit arrowDeletionRequested(playerId, arrowId); + } +} + +void GameScene::onCardZoneChanged(CardItem *card, bool sameZone) +{ + QList toDelete; + for (auto *arrow : arrowRegistry.all()) { + if (arrow->getStartItem() == card || arrow->getTargetItem() == card) { + if (sameZone) { + arrow->updatePath(); + } else { + toDelete.append(arrow); + } + } + } + for (auto *arrow : toDelete) { + deleteArrow(arrow->getCreatorId(), arrow->getId()); + } +} + +void GameScene::clearArrowsForPlayer(int playerId) +{ + for (int arrowId : arrowRegistry.idsForPlayer(playerId)) { + emit requestArrowDeletion(playerId, arrowId); + } +} + +void GameScene::clearArrowsForPlayerLocally(int playerId) +{ + for (int arrowId : arrowRegistry.idsForPlayer(playerId)) { + arrowRegistry.take(playerId, arrowId)->delArrow(); + } +} + // ---------- Hover Handling ---------- void GameScene::updateHover(const QPointF &scenePos) @@ -343,18 +546,38 @@ void GameScene::updateHover(const QPointF &scenePos) void GameScene::updateHoveredCard(CardItem *newCard) { - if (hoveredCard && (newCard != hoveredCard)) - hoveredCard->setHovered(false); - if (newCard && (newCard != hoveredCard)) - newCard->setHovered(true); + if (hoveredCard && (newCard != hoveredCard)) { + endCardHover(hoveredCard); + } + if (newCard && (newCard != hoveredCard)) { + beginCardHover(newCard); + } hoveredCard = newCard; } +void GameScene::beginCardHover(CardItem *card) +{ + card->setHovered(true); + if (auto *zone = SelectZone::findOwningSelectZone(card)) { + zone->escapeClipForHover(card); + } +} + +void GameScene::endCardHover(CardItem *card) +{ + if (auto *zone = SelectZone::findOwningSelectZone(card)) { + zone->restoreClipAfterHover(card); + } + card->setHovered(false); +} + CardZone *GameScene::findTopmostZone(const QList &items) { - for (QGraphicsItem *item : items) - if (auto *zone = qgraphicsitem_cast(item)) + for (QGraphicsItem *item : items) { + if (auto *zone = qgraphicsitem_cast(item)) { return zone; + } + } return nullptr; } @@ -365,14 +588,17 @@ CardItem *GameScene::findTopmostCardInZone(const QList &items, for (QGraphicsItem *item : items) { CardItem *card = qgraphicsitem_cast(item); - if (!card) + if (!card) { continue; + } if (card->getAttachedTo()) { - if (card->getAttachedTo()->getZone() != zone->getLogic()) + if (card->getAttachedTo()->getZone() != zone->getLogic()) { continue; - } else if (card->getZone() != zone->getLogic()) + } + } else if (card->getZone() != zone->getLogic()) { continue; + } if (card->getRealZValue() > maxZ) { maxZ = card->getRealZValue(); @@ -394,7 +620,7 @@ CardItem *GameScene::findTopmostCardInZone(const QList &items, * If an identical view exists, it is closed. Otherwise, a new ZoneViewWidget is created * and positioned based on zone type. */ -void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed) +void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed) { for (auto &view : zoneViews) { ZoneViewZone *temp = view->getZone(); @@ -411,12 +637,13 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView); addItem(item); - if (zoneName == ZoneNames::GRAVE) + if (zoneName == ZoneNames::GRAVE) { item->setPos(360, 100); - else if (zoneName == ZoneNames::EXILE) + } else if (zoneName == ZoneNames::EXILE) { item->setPos(380, 120); - else + } else { item->setPos(340, 80); + } } /** @@ -426,7 +653,7 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb * @param cardList List of cards to show. * @param withWritePermission Whether edits are allowed. */ -void GameScene::addRevealedZoneView(Player *player, +void GameScene::addRevealedZoneView(PlayerLogic *player, CardZoneLogic *zone, const QList &cardList, bool withWritePermission) @@ -453,8 +680,9 @@ void GameScene::removeZoneView(ZoneViewWidget *item) */ void GameScene::clearViews() { - while (!zoneViews.isEmpty()) + while (!zoneViews.isEmpty()) { zoneViews.first()->close(); + } } /** @@ -462,8 +690,9 @@ void GameScene::clearViews() */ void GameScene::closeMostRecentZoneView() { - if (!zoneViews.isEmpty()) + if (!zoneViews.isEmpty()) { zoneViews.last()->close(); + } } // ---------- View Transforms ---------- @@ -482,8 +711,11 @@ QTransform GameScene::getViewportTransform() const bool GameScene::event(QEvent *event) { - if (event->type() == QEvent::GraphicsSceneMouseMove) + if (event->type() == QEvent::GraphicsSceneMouseMove) { updateHover(static_cast(event)->scenePos()); + } else if (event->type() == QEvent::Leave) { + updateHoveredCard(nullptr); + } return QGraphicsScene::event(event); } @@ -493,25 +725,29 @@ void GameScene::timerEvent(QTimerEvent * /*event*/) QMutableSetIterator i(cardsToAnimate); while (i.hasNext()) { i.next(); - if (!i.value()->animationEvent()) + if (!i.value()->animationEvent()) { i.remove(); + } } - if (cardsToAnimate.isEmpty()) + if (cardsToAnimate.isEmpty()) { animationTimer->stop(); + } } void GameScene::registerAnimationItem(AbstractCardItem *card) { cardsToAnimate.insert(static_cast(card)); - if (!animationTimer->isActive()) + if (!animationTimer->isActive()) { animationTimer->start(10, this); + } } void GameScene::unregisterAnimationItem(AbstractCardItem *card) { cardsToAnimate.remove(static_cast(card)); - if (cardsToAnimate.isEmpty()) + if (cardsToAnimate.isEmpty()) { animationTimer->stop(); + } } // ---------- Rubber Band ---------- diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game_graphics/game_scene.h similarity index 64% rename from cockatrice/src/game/game_scene.h rename to cockatrice/src/game_graphics/game_scene.h index f08e83aa4..74e979556 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game_graphics/game_scene.h @@ -1,7 +1,10 @@ #ifndef GAMESCENE_H #define GAMESCENE_H -#include "zones/logic/card_zone_logic.h" +#include "../game/arrow_registry.h" +#include "../game/board/arrow_data.h" +#include "../game/zones/card_zone_logic.h" +#include "board/arrow_item.h" #include #include @@ -12,7 +15,7 @@ inline Q_LOGGING_CATEGORY(GameSceneLog, "game_scene"); inline Q_LOGGING_CATEGORY(GameScenePlayerAdditionRemovalLog, "game_scene.player_addition_removal"); -class Player; +class PlayerLogic; class PlayerGraphicsItem; class ZoneViewWidget; class CardZone; @@ -41,8 +44,9 @@ private: static const int playerAreaSpacing = 5; ///< Space between player areas PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases - QList players; ///< All player graphics items + QMap playerViews; ///< ID lookup for player graphics items QList> playersByColumn; ///< Players organized by column + ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items QList zoneViews; ///< Active zone view widgets QSize viewSize; ///< Current view size QPointer hoveredCard; ///< Currently hovered card @@ -56,6 +60,14 @@ private: */ void updateHover(const QPointF &scenePos); + /** + * @brief Activates hover state and escapes the card from its clip container + * so hover scaling is visible beyond zone bounds. + */ + void beginCardHover(CardItem *card); + /** @brief Deactivates hover state and restores the card to its clip container. */ + void endCardHover(CardItem *card); + public: /** * @brief Constructs the GameScene. @@ -64,23 +76,36 @@ public: */ explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); - /** Destructor, cleans up timer and zone views. */ + /** @brief Destructor, cleans up timer and zone views. */ ~GameScene() override; - /** Updates UI text for all zone views. */ + /** @brief Updates UI text for all zone views. */ void retranslateUi(); + /** @brief Gets all selected CardItems. */ + QList selectedCards() const; + /** * @brief Adds a player to the scene and stores their graphics item. * @param player Player to add. */ - void addPlayer(Player *player); + void addPlayer(PlayerLogic *player); /** * @brief Removes a player from the scene. * @param player Player to remove. */ - void removePlayer(Player *player); + void removePlayer(PlayerLogic *player); + + QMap getPlayers() const + { + return playerViews; + } + + PlayerGraphicsItem *viewForPlayer(int playerId) + { + return playerViews.value(playerId); + } /** * @brief Adjusts the global rotation offset for player layout. @@ -88,7 +113,7 @@ public: */ void adjustPlayerRotation(int rotationAdjustment); - /** Recomputes the layout of players and the scene size. */ + /** @brief Recomputes the layout of players and the scene size. */ void rearrange(); /** @@ -102,7 +127,7 @@ public: * @param firstPlayerIndex Output index of first local player. * @return List of active players. */ - QList collectActivePlayers(int &firstPlayerIndex) const; + QList collectActivePlayers(int &firstPlayerIndex) const; /** * @brief Rotates the list of players for layout. @@ -110,7 +135,7 @@ public: * @param firstPlayerIndex Index of first local player. * @return Rotated list. */ - QList rotatePlayers(const QList &players, int firstPlayerIndex) const; + QList rotatePlayers(const QList &players, int firstPlayerIndex) const; /** * @brief Determines the number of columns to display players in. @@ -125,7 +150,7 @@ public: * @param columns Number of columns to split into. * @return Calculated scene size. */ - QSizeF computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns); + QSizeF computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns); /** * @brief Computes the minimum width for each column based on player minimum widths. @@ -148,56 +173,73 @@ public: */ void resizeColumnsAndPlayers(const QList &minWidthByColumn, qreal newWidth); - /** Finds the topmost card zone under the cursor. */ + /** @brief Finds the topmost card zone under the cursor. */ static CardZone *findTopmostZone(const QList &items); - /** Finds the topmost card in a given zone, considering attachments and Z-order. */ + /** @brief Finds the topmost card in a given zone, considering attachments and Z-order. */ static CardItem *findTopmostCardInZone(const QList &items, CardZone *zone); - /** Updates hovered card highlighting. */ + /** @brief Updates hovered card highlighting. */ void updateHoveredCard(CardItem *newCard); - /** Registers a card for animation updates. */ + /** @brief Registers a card for animation updates. */ void registerAnimationItem(AbstractCardItem *card); - /** Unregisters a card from animation updates. */ + /** @brief Unregisters a card from animation updates. */ void unregisterAnimationItem(AbstractCardItem *card); void startRubberBand(const QPointF &selectionOrigin); void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); public slots: - /** Toggles a zone view for a player. */ - void toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed = false); + void onCardSelectionChanged(AbstractCardItem *card, bool selected); + void onCardRightClicked(AbstractCardItem *card, QPoint screenPos); + void playSelected(AbstractCardItem *card); + void playSelectedFaceDown(AbstractCardItem *card); + void hideSelected(AbstractCardItem *card); + /** @brief Toggles a zone view for a player. */ + void toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed = false); - /** Adds a revealed zone view (for shown cards). */ - void addRevealedZoneView(Player *player, + /** @brief Adds a revealed zone view (for shown cards). */ + void addRevealedZoneView(PlayerLogic *player, CardZoneLogic *zone, const QList &cardList, bool withWritePermission); - /** Removes a zone view widget from the scene. */ + /** @brief Removes a zone view widget from the scene. */ void removeZoneView(ZoneViewWidget *item); - /** Closes all zone views. */ + /** @brief Closes all zone views. */ void clearViews(); - /** Closes the most recently added zone view. */ + /** @brief Closes the most recently added zone view. */ void closeMostRecentZoneView(); QTransform getViewTransform() const; QTransform getViewportTransform() const; + /// Directly modifies the scene + void addArrow(QSharedPointer data); + void deleteArrow(int playerId, int arrowId); + void clearArrowsForPlayer(int playerId); + void clearArrowsForPlayerLocally(int playerId); + + /// Queues up arrow deletion but doesn't directly modify the scene + void requestArrowDeletion(int playerId, int arrowId); + + void onCardZoneChanged(CardItem *card, bool sameZone); + protected: - /** Handles hover updates. */ + /** @brief Handles hover updates. */ bool event(QEvent *event) override; - /** Handles animation timer updates. */ + /** @brief Handles animation timer updates. */ void timerEvent(QTimerEvent *event) override; signals: void sigStartRubberBand(const QPointF &selectionOrigin); void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount); void sigStopRubberBand(); + void arrowDeletionRequested(int creatorId, int arrowId); }; #endif diff --git a/cockatrice/src/game/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp similarity index 98% rename from cockatrice/src/game/game_view.cpp rename to cockatrice/src/game_graphics/game_view.cpp index ce53828a7..4ba41cffb 100644 --- a/cockatrice/src/game/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -75,8 +75,9 @@ void GameView::resizeEvent(QResizeEvent *event) QGraphicsView::resizeEvent(event); GameScene *s = dynamic_cast(scene()); - if (s) + if (s) { s->processViewSizeChange(event->size()); + } updateSceneRect(scene()->sceneRect()); updateTotalSelectionCount(event->size()); @@ -89,8 +90,9 @@ void GameView::updateSceneRect(const QRectF &rect) void GameView::startRubberBand(const QPointF &_selectionOrigin) { - if (!rubberBand) + if (!rubberBand) { return; + } selectionOrigin = _selectionOrigin; rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); @@ -99,8 +101,9 @@ void GameView::startRubberBand(const QPointF &_selectionOrigin) void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) { - if (!rubberBand) + if (!rubberBand) { return; + } constexpr int kLabelPaddingInPixels = 4; @@ -145,8 +148,9 @@ void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) void GameView::stopRubberBand() { - if (!rubberBand) + if (!rubberBand) { return; + } rubberBand->hide(); dragCountLabel->hide(); diff --git a/cockatrice/src/game/game_view.h b/cockatrice/src/game_graphics/game_view.h similarity index 96% rename from cockatrice/src/game/game_view.h rename to cockatrice/src/game_graphics/game_view.h index a77ab9257..15abad9af 100644 --- a/cockatrice/src/game/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -1,8 +1,8 @@ /** * @file game_view.h * @ingroup GameGraphics - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef GAMEVIEW_H #define GAMEVIEW_H diff --git a/cockatrice/src/game/hand_counter.cpp b/cockatrice/src/game_graphics/hand_counter.cpp similarity index 100% rename from cockatrice/src/game/hand_counter.cpp rename to cockatrice/src/game_graphics/hand_counter.cpp diff --git a/cockatrice/src/game/hand_counter.h b/cockatrice/src/game_graphics/hand_counter.h similarity index 84% rename from cockatrice/src/game/hand_counter.h rename to cockatrice/src/game_graphics/hand_counter.h index 2c0175ecc..9aa65d514 100644 --- a/cockatrice/src/game/hand_counter.h +++ b/cockatrice/src/game_graphics/hand_counter.h @@ -1,14 +1,14 @@ /** * @file hand_counter.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef HANDCOUNTER_H #define HANDCOUNTER_H -#include "../game_graphics/board/abstract_graphics_item.h" -#include "../game_graphics/board/graphics_item_type.h" +#include "board/abstract_graphics_item.h" +#include "board/graphics_item_type.h" #include diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game_graphics/log/message_log_widget.cpp similarity index 90% rename from cockatrice/src/game/log/message_log_widget.cpp rename to cockatrice/src/game_graphics/log/message_log_widget.cpp index c38e433eb..ccd903b04 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game_graphics/log/message_log_widget.cpp @@ -1,13 +1,13 @@ #include "message_log_widget.h" +#include "../../client/settings/card_counter_settings.h" #include "../../client/sound_engine.h" +#include "../../game/phase.h" +#include "../../game/player/player_logic.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../board/card_item.h" #include "../board/translate_counter_name.h" -#include "../phase.h" -#include "../player/player.h" -#include <../../client/settings/card_counter_settings.h> #include #include #include @@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position fromStr = tr(" from the top of their library"); } } - } else if (position >= zone->getCards().size() - 1) { + } else if (position == zone->getCards().size()) { if (cardName.isEmpty()) { if (ownerChange) { cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName()); @@ -105,7 +105,7 @@ void MessageLogWidget::containerProcessingStarted(const GameEventContext &contex } } -void MessageLogWidget::logAlwaysRevealTopCard(Player *player, CardZoneLogic *zone, bool reveal) +void MessageLogWidget::logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal) { appendHtmlServerMessage((reveal ? tr("%1 is now keeping the top card %2 revealed.") : tr("%1 is not revealing the top card %2 any longer.")) @@ -113,7 +113,7 @@ void MessageLogWidget::logAlwaysRevealTopCard(Player *player, CardZoneLogic *zon .arg(zone->getTranslatedName(true, CaseTopCardsOfZone))); } -void MessageLogWidget::logAlwaysLookAtTopCard(Player *player, CardZoneLogic *zone, bool reveal) +void MessageLogWidget::logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal) { appendHtmlServerMessage((reveal ? tr("%1 can now look at top card %2 at any time.") : tr("%1 no longer can look at top card %2 at any time.")) @@ -121,7 +121,10 @@ void MessageLogWidget::logAlwaysLookAtTopCard(Player *player, CardZoneLogic *zon .arg(zone->getTranslatedName(true, CaseTopCardsOfZone))); } -void MessageLogWidget::logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName) +void MessageLogWidget::logAttachCard(PlayerLogic *player, + QString cardName, + PlayerLogic *targetPlayer, + QString targetCardName) { appendHtmlServerMessage(tr("%1 attaches %2 to %3's %4.") .arg(sanitizeHtml(player->getPlayerInfo()->getName())) @@ -148,7 +151,7 @@ void MessageLogWidget::logUnconcede(int playerId) true); } -void MessageLogWidget::logConnectionStateChanged(Player *player, bool connectionState) +void MessageLogWidget::logConnectionStateChanged(PlayerLogic *player, bool connectionState) { if (connectionState) { soundEngine->playSound("player_reconnect"); @@ -161,10 +164,10 @@ void MessageLogWidget::logConnectionStateChanged(Player *player, bool connection } } -void MessageLogWidget::logCreateArrow(Player *player, - Player *startPlayer, +void MessageLogWidget::logCreateArrow(PlayerLogic *player, + PlayerLogic *startPlayer, QString startCard, - Player *targetPlayer, + PlayerLogic *targetPlayer, QString targetCard, bool playerTarget) { @@ -220,7 +223,7 @@ void MessageLogWidget::logCreateArrow(Player *player, } } -void MessageLogWidget::logCreateToken(Player *player, QString cardName, QString pt, bool faceDown) +void MessageLogWidget::logCreateToken(PlayerLogic *player, QString cardName, QString pt, bool faceDown) { if (faceDown) { appendHtmlServerMessage( @@ -233,7 +236,7 @@ void MessageLogWidget::logCreateToken(Player *player, QString cardName, QString } } -void MessageLogWidget::logDeckSelect(Player *player, QString deckHash, int sideboardSize) +void MessageLogWidget::logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize) { if (sideboardSize < 0) { appendHtmlServerMessage( @@ -246,13 +249,13 @@ void MessageLogWidget::logDeckSelect(Player *player, QString deckHash, int sideb } } -void MessageLogWidget::logDestroyCard(Player *player, QString cardName) +void MessageLogWidget::logDestroyCard(PlayerLogic *player, QString cardName) { appendHtmlServerMessage( tr("%1 destroys %2.").arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(std::move(cardName)))); } -void MessageLogWidget::logMoveCard(Player *player, +void MessageLogWidget::logMoveCard(PlayerLogic *player, CardItem *card, CardZoneLogic *startZone, int oldX, @@ -359,7 +362,7 @@ void MessageLogWidget::logMoveCard(Player *player, appendHtmlServerMessage(message); } -void MessageLogWidget::logDrawCards(Player *player, int number, bool deckIsEmpty) +void MessageLogWidget::logDrawCards(PlayerLogic *player, int number, bool deckIsEmpty) { soundEngine->playSound("draw_card"); if (currentContext == MessageContext_Mulligan) { @@ -376,7 +379,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number, bool deckIsEmpty } } -void MessageLogWidget::logDumpZone(Player *player, CardZoneLogic *zone, int numberCards, bool isReversed) +void MessageLogWidget::logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed) { if (numberCards == -1) { appendHtmlServerMessage(tr("%1 is looking at %2.") @@ -392,7 +395,7 @@ void MessageLogWidget::logDumpZone(Player *player, CardZoneLogic *zone, int numb } } -void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown) +void MessageLogWidget::logFlipCard(PlayerLogic *player, QString cardName, bool faceDown) { if (faceDown) { appendHtmlServerMessage( @@ -418,7 +421,7 @@ void MessageLogWidget::logGameFlooded() appendMessage(tr("You are flooding the game. Please wait a couple of seconds.")); } -void MessageLogWidget::logJoin(Player *player) +void MessageLogWidget::logJoin(PlayerLogic *player) { soundEngine->playSound("player_join"); appendHtmlServerMessage(tr("%1 has joined the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName()))); @@ -435,7 +438,7 @@ void MessageLogWidget::logKicked() appendHtmlServerMessage(tr("You have been kicked out of the game."), true); } -void MessageLogWidget::logLeave(Player *player, QString reason) +void MessageLogWidget::logLeave(PlayerLogic *player, QString reason) { soundEngine->playSound("player_leave"); appendHtmlServerMessage(tr("%1 has left the game (%2).") @@ -450,13 +453,13 @@ void MessageLogWidget::logLeaveSpectator(QString name, QString reason) .arg(sanitizeHtml(std::move(name)), sanitizeHtml(std::move(reason)))); } -void MessageLogWidget::logNotReadyStart(Player *player) +void MessageLogWidget::logNotReadyStart(PlayerLogic *player) { appendHtmlServerMessage( tr("%1 is not ready to start the game any more.").arg(sanitizeHtml(player->getPlayerInfo()->getName()))); } -void MessageLogWidget::logMulligan(Player *player, int number) +void MessageLogWidget::logMulligan(PlayerLogic *player, int number) { if (!player) { return; @@ -476,16 +479,16 @@ void MessageLogWidget::logReplayStarted(int gameId) appendHtmlServerMessage(tr("You are watching a replay of game #%1.").arg(gameId)); } -void MessageLogWidget::logReadyStart(Player *player) +void MessageLogWidget::logReadyStart(PlayerLogic *player) { appendHtmlServerMessage(tr("%1 is ready to start the game.").arg(sanitizeHtml(player->getPlayerInfo()->getName()))); } -void MessageLogWidget::logRevealCards(Player *player, +void MessageLogWidget::logRevealCards(PlayerLogic *player, CardZoneLogic *zone, int cardId, QString cardName, - Player *otherPlayer, + PlayerLogic *otherPlayer, bool faceDown, int amount, bool isLentToAnotherPlayer) @@ -568,14 +571,14 @@ void MessageLogWidget::logRevealCards(Player *player, } } -void MessageLogWidget::logReverseTurn(Player *player, bool reversed) +void MessageLogWidget::logReverseTurn(PlayerLogic *player, bool reversed) { appendHtmlServerMessage(tr("%1 reversed turn order, now it's %2.") .arg(sanitizeHtml(player->getPlayerInfo()->getName())) .arg(reversed ? tr("reversed") : tr("normal"))); } -void MessageLogWidget::logRollDie(Player *player, int sides, const QList &rolls) +void MessageLogWidget::logRollDie(PlayerLogic *player, int sides, const QList &rolls) { if (rolls.length() == 1) { const auto roll = rolls.at(0); @@ -612,7 +615,7 @@ void MessageLogWidget::logRollDie(Player *player, int sides, const QList & soundEngine->playSound("roll_dice"); } -void MessageLogWidget::logSay(Player *player, QString message) +void MessageLogWidget::logSay(PlayerLogic *player, QString message) { appendMessage(std::move(message), {}, *player->getPlayerInfo()->getUserInfo(), true); } @@ -627,13 +630,13 @@ void MessageLogWidget::logSetActivePhase(int phaseNumber) phase.getName() + ""); } -void MessageLogWidget::logSetActivePlayer(Player *player) +void MessageLogWidget::logSetActivePlayer(PlayerLogic *player) { appendHtml("
" + QDateTime::currentDateTime().toString("[hh:mm:ss] ") + QString(tr("%1's turn.")).arg(player->getPlayerInfo()->getName()) + "
"); } -void MessageLogWidget::logSetAnnotation(Player *player, CardItem *card, QString newAnnotation) +void MessageLogWidget::logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation) { appendHtmlServerMessage( QString(tr("%1 sets annotation of %2 to %3.")) @@ -642,25 +645,27 @@ void MessageLogWidget::logSetAnnotation(Player *player, CardItem *card, QString .arg(QString(""%1"").arg(sanitizeHtml(std::move(newAnnotation))))); } -void MessageLogWidget::logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue) +void MessageLogWidget::logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue) { QString finalStr; int delta = abs(oldValue - value); if (value > oldValue) { - finalStr = tr("%1 places %2 \"%3\" counter(s) on %4 (now %5).", "", delta); + finalStr = tr("%1 places %2 %3%4 counter(s) on %5 (now %6).", "", delta); } else { - finalStr = tr("%1 removes %2 \"%3\" counter(s) from %4 (now %5).", "", delta); + finalStr = tr("%1 removes %2 %3%4 counter(s) from %5 (now %6).", "", delta); } auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + QString hex = cardCounterSettings.color(counterId).name(); appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName())) .arg("" + QString::number(delta) + "") + .arg("") .arg(cardCounterSettings.displayName(counterId)) .arg(cardLink(std::move(cardName))) .arg(value)); } -void MessageLogWidget::logSetCounter(Player *player, QString counterName, int value, int oldValue) +void MessageLogWidget::logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue) { if (counterName == "life") { soundEngine->playSound("life_change"); @@ -675,7 +680,7 @@ void MessageLogWidget::logSetCounter(Player *player, QString counterName, int va .arg(value - oldValue)); } -void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap) +void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap) { QString str; if (doesntUntap) { @@ -686,7 +691,7 @@ void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool do appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(card->getName()))); } -void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT) +void MessageLogWidget::logSetPT(PlayerLogic *player, CardItem *card, QString newPT) { if (currentContext == MessageContext_MoveCard) { return; @@ -713,7 +718,7 @@ void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT) } } -void MessageLogWidget::logSetSideboardLock(Player *player, bool locked) +void MessageLogWidget::logSetSideboardLock(PlayerLogic *player, bool locked) { if (locked) { appendHtmlServerMessage( @@ -724,7 +729,7 @@ void MessageLogWidget::logSetSideboardLock(Player *player, bool locked) } } -void MessageLogWidget::logSetTapped(Player *player, CardItem *card, bool tapped) +void MessageLogWidget::logSetTapped(PlayerLogic *player, CardItem *card, bool tapped) { if (currentContext == MessageContext_MoveCard) { return; @@ -747,7 +752,7 @@ void MessageLogWidget::logSetTapped(Player *player, CardItem *card, bool tapped) } } -void MessageLogWidget::logShuffle(Player *player, CardZoneLogic *zone, int start, int end) +void MessageLogWidget::logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end) { if (currentContext == MessageContext_Mulligan) { return; @@ -784,14 +789,14 @@ void MessageLogWidget::logSpectatorSay(const ServerInfo_User &spectator, QString appendMessage(std::move(message), {}, spectator, false); } -void MessageLogWidget::logUnattachCard(Player *player, QString cardName) +void MessageLogWidget::logUnattachCard(PlayerLogic *player, QString cardName) { appendHtmlServerMessage(tr("%1 unattaches %2.") .arg(sanitizeHtml(player->getPlayerInfo()->getName())) .arg(cardLink(std::move(cardName)))); } -void MessageLogWidget::logUndoDraw(Player *player, QString cardName) +void MessageLogWidget::logUndoDraw(PlayerLogic *player, QString cardName) { if (cardName.isEmpty()) { appendHtmlServerMessage(tr("%1 undoes their last draw.").arg(sanitizeHtml(player->getPlayerInfo()->getName()))); @@ -803,6 +808,12 @@ void MessageLogWidget::logUndoDraw(Player *player, QString cardName) } } +void MessageLogWidget::logUndoDrawFailed(PlayerLogic *player) +{ + appendHtmlServerMessage( + tr("%1 failed to undo their last draw.").arg(sanitizeHtml(player->getPlayerInfo()->getName()))); +} + void MessageLogWidget::setContextJudgeName(QString name) { messagePrefix = QString(""); @@ -836,6 +847,7 @@ void MessageLogWidget::connectToPlayerEventHandler(PlayerEventHandler *playerEve connect(playerEventHandler, &PlayerEventHandler::logDumpZone, this, &MessageLogWidget::logDumpZone); connect(playerEventHandler, &PlayerEventHandler::logDrawCards, this, &MessageLogWidget::logDrawCards); connect(playerEventHandler, &PlayerEventHandler::logUndoDraw, this, &MessageLogWidget::logUndoDraw); + connect(playerEventHandler, &PlayerEventHandler::logUndoDrawFailed, this, &MessageLogWidget::logUndoDrawFailed); connect(playerEventHandler, &PlayerEventHandler::logRevealCards, this, &MessageLogWidget::logRevealCards); connect(playerEventHandler, &PlayerEventHandler::logAlwaysRevealTopCard, this, &MessageLogWidget::logAlwaysRevealTopCard); diff --git a/cockatrice/src/game_graphics/log/message_log_widget.h b/cockatrice/src/game_graphics/log/message_log_widget.h new file mode 100644 index 000000000..a145d358d --- /dev/null +++ b/cockatrice/src/game_graphics/log/message_log_widget.h @@ -0,0 +1,109 @@ +/** + * @file message_log_widget.h + * @ingroup GameWidgets + */ +//! \todo Document this file. + +#ifndef MESSAGELOGWIDGET_H +#define MESSAGELOGWIDGET_H + +#include "../../game/zones/card_zone_logic.h" +#include "../../interface/widgets/server/chat_view/chat_view.h" + +class AbstractGame; +class CardItem; +class GameEventContext; +class PlayerLogic; +class PlayerEventHandler; + +class MessageLogWidget : public ChatView +{ + Q_OBJECT +private: + enum MessageContext + { + MessageContext_None, + MessageContext_MoveCard, + MessageContext_Mulligan + }; + + MessageContext currentContext; + QString messagePrefix, messageSuffix; + + static QPair getFromStr(CardZoneLogic *zone, QString cardName, int position, bool ownerChange); + +public: + void connectToPlayerEventHandler(PlayerEventHandler *player); + MessageLogWidget(TabSupervisor *_tabSupervisor, AbstractGame *_game, QWidget *parent = nullptr); + +public slots: + void containerProcessingDone(); + void containerProcessingStarted(const GameEventContext &context); + void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + void logAttachCard(PlayerLogic *player, QString cardName, PlayerLogic *targetPlayer, QString targetCardName); + void logConcede(int playerId); + void logUnconcede(int playerId); + void logConnectionStateChanged(PlayerLogic *player, bool connectionState); + void logCreateArrow(PlayerLogic *player, + PlayerLogic *startPlayer, + QString startCard, + PlayerLogic *targetPlayer, + QString targetCard, + bool playerTarget); + void logCreateToken(PlayerLogic *player, QString cardName, QString pt, bool faceDown); + void logDeckSelect(PlayerLogic *player, QString deckHash, int sideboardSize); + void logDestroyCard(PlayerLogic *player, QString cardName); + void logDrawCards(PlayerLogic *player, int number, bool deckIsEmpty); + void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); + void logFlipCard(PlayerLogic *player, QString cardName, bool faceDown); + void logGameClosed(); + void logGameStart(); + void logGameFlooded(); + void logJoin(PlayerLogic *player); + void logJoinSpectator(QString name); + void logKicked(); + void logLeave(PlayerLogic *player, QString reason); + void logLeaveSpectator(QString name, QString reason); + void logNotReadyStart(PlayerLogic *player); + void logMoveCard(PlayerLogic *player, + CardItem *card, + CardZoneLogic *startZone, + int oldX, + CardZoneLogic *targetZone, + int newX); + void logMulligan(PlayerLogic *player, int number); + void logReplayStarted(int gameId); + void logReadyStart(PlayerLogic *player); + void logRevealCards(PlayerLogic *player, + CardZoneLogic *zone, + int cardId, + QString cardName, + PlayerLogic *otherPlayer, + bool faceDown, + int amount, + bool isLentToAnotherPlayer); + void logReverseTurn(PlayerLogic *player, bool reversed); + void logRollDie(PlayerLogic *player, int sides, const QList &rolls); + void logSay(PlayerLogic *player, QString message); + void logSetActivePhase(int phase); + void logSetActivePlayer(PlayerLogic *player); + void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation); + void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue); + void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue); + void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap); + void logSetPT(PlayerLogic *player, CardItem *card, QString newPT); + void logSetSideboardLock(PlayerLogic *player, bool locked); + void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped); + void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end); + void logSpectatorSay(const ServerInfo_User &spectator, QString message); + void logUnattachCard(PlayerLogic *player, QString cardName); + void logUndoDraw(PlayerLogic *player, QString cardName); + void logUndoDrawFailed(PlayerLogic *player); + void setContextJudgeName(QString player); + void appendHtmlServerMessage(const QString &html, + bool optionalIsBold = false, + QString optionalFontColor = QString()) override; +}; + +#endif diff --git a/cockatrice/src/game/phases_toolbar.cpp b/cockatrice/src/game_graphics/phases_toolbar.cpp similarity index 95% rename from cockatrice/src/game/phases_toolbar.cpp rename to cockatrice/src/game_graphics/phases_toolbar.cpp index 2341a1d7f..3361f9d55 100644 --- a/cockatrice/src/game/phases_toolbar.cpp +++ b/cockatrice/src/game_graphics/phases_toolbar.cpp @@ -21,8 +21,9 @@ PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_ activeAnimationTimer = new QTimer(this); connect(activeAnimationTimer, &QTimer::timeout, this, &PhaseButton::updateAnimation); activeAnimationTimer->setSingleShot(false); - } else + } else { activeAnimationCounter = 9; + } setCacheMode(DeviceCoordinateCache); } @@ -63,8 +64,9 @@ void PhaseButton::setWidth(double _width) void PhaseButton::setActive(bool _active) { - if ((active == _active) || !highlightable) + if ((active == _active) || !highlightable) { return; + } active = _active; activeAnimationTimer->start(25); @@ -72,8 +74,9 @@ void PhaseButton::setActive(bool _active) void PhaseButton::updateAnimation() { - if (!highlightable) + if (!highlightable) { return; + } // the counter ticks up to 10 when active and down to 0 when inactive if (active && activeAnimationCounter < 10) { @@ -99,8 +102,9 @@ void PhaseButton::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/) void PhaseButton::triggerDoubleClickAction() { - if (doubleClickAction) + if (doubleClickAction) { doubleClickAction->trigger(); + } } PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) @@ -126,8 +130,9 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton << combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton; - for (auto &i : buttonList) + for (auto &i : buttonList) { connect(i, &PhaseButton::clicked, this, &PhasesToolbar::phaseButtonClicked); + } nextTurnButton = new PhaseButton("nextturn", this, nullptr, false); connect(nextTurnButton, &PhaseButton::clicked, this, &PhasesToolbar::actNextTurn); @@ -144,8 +149,9 @@ QRectF PhasesToolbar::boundingRect() const void PhasesToolbar::retranslateUi() { - for (int i = 0; i < buttonList.size(); ++i) + for (int i = 0; i < buttonList.size(); ++i) { buttonList[i]->setToolTip(getLongPhaseName(i)); + } } QString PhasesToolbar::getLongPhaseName(int phase) const @@ -187,8 +193,9 @@ const double PhasesToolbar::marginSize = 3; void PhasesToolbar::rearrangeButtons() { - for (auto &i : buttonList) + for (auto &i : buttonList) { i->setWidth(symbolSize); + } nextTurnButton->setWidth(symbolSize); double y = marginSize; @@ -226,11 +233,13 @@ void PhasesToolbar::setHeight(double _height) void PhasesToolbar::setActivePhase(int phase) { - if (phase >= buttonList.size()) + if (phase >= buttonList.size()) { return; + } - for (int i = 0; i < buttonList.size(); ++i) + for (int i = 0; i < buttonList.size(); ++i) { buttonList[i]->setActive(i == phase); + } } void PhasesToolbar::triggerPhaseAction(int phase) @@ -243,8 +252,9 @@ void PhasesToolbar::triggerPhaseAction(int phase) void PhasesToolbar::phaseButtonClicked() { auto *button = qobject_cast(sender()); - if (button->getActive()) + if (button->getActive()) { button->triggerDoubleClickAction(); + } Command_SetActivePhase cmd; cmd.set_phase(static_cast(buttonList.indexOf(button))); diff --git a/cockatrice/src/game/phases_toolbar.h b/cockatrice/src/game_graphics/phases_toolbar.h similarity index 96% rename from cockatrice/src/game/phases_toolbar.h rename to cockatrice/src/game_graphics/phases_toolbar.h index 215a97dd1..39884ef75 100644 --- a/cockatrice/src/game/phases_toolbar.h +++ b/cockatrice/src/game_graphics/phases_toolbar.h @@ -2,13 +2,13 @@ * @file phases_toolbar.h * @ingroup GameGraphics * @ingroup GameWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PHASESTOOLBAR_H #define PHASESTOOLBAR_H -#include "../game_graphics/board/abstract_graphics_item.h" +#include "board/abstract_graphics_item.h" #include #include @@ -21,7 +21,7 @@ namespace protobuf class Message; } } // namespace google -class Player; +class PlayerLogic; class GameCommand; class PhaseButton : public QObject, public QGraphicsItem diff --git a/cockatrice/src/game/player/card_menu_action_type.h b/cockatrice/src/game_graphics/player/card_menu_action_type.h similarity index 95% rename from cockatrice/src/game/player/card_menu_action_type.h rename to cockatrice/src/game_graphics/player/card_menu_action_type.h index 1b63674fa..4cae22716 100644 --- a/cockatrice/src/game/player/card_menu_action_type.h +++ b/cockatrice/src/game_graphics/player/card_menu_action_type.h @@ -1,8 +1,8 @@ /** * @file card_menu_action_type.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_CARD_MENU_ACTION_TYPE_H #define COCKATRICE_CARD_MENU_ACTION_TYPE_H diff --git a/cockatrice/src/game/player/menu/abstract_player_component.h b/cockatrice/src/game_graphics/player/menu/abstract_player_component.h similarity index 78% rename from cockatrice/src/game/player/menu/abstract_player_component.h rename to cockatrice/src/game_graphics/player/menu/abstract_player_component.h index 989300d41..ee310c428 100644 --- a/cockatrice/src/game/player/menu/abstract_player_component.h +++ b/cockatrice/src/game_graphics/player/menu/abstract_player_component.h @@ -19,13 +19,13 @@ class AbstractPlayerComponent public: virtual ~AbstractPlayerComponent() = default; - /// Bind keyboard shortcuts. Called when this player gains focus. + /** @brief Bind keyboard shortcuts. Called when this player gains focus. */ virtual void setShortcutsActive() = 0; - /// Unbind keyboard shortcuts. Called when this player loses focus. + /** @brief Unbind keyboard shortcuts. Called when this player loses focus. */ virtual void setShortcutsInactive() = 0; - /// Retranslate all user-visible strings. Called on language change. + /** @brief Retranslate all user-visible strings. Called on language change. */ virtual void retranslateUi() = 0; }; diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game_graphics/player/menu/card_menu.cpp similarity index 74% rename from cockatrice/src/game/player/menu/card_menu.cpp rename to cockatrice/src/game_graphics/player/menu/card_menu.cpp index 66ca5e46b..aa94c3be7 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/card_menu.cpp @@ -3,89 +3,121 @@ #include "../../../client/settings/card_counter_settings.h" #include "../../../interface/widgets/tabs/tab_game.h" #include "../../board/card_item.h" -#include "../../zones/logic/view_zone_logic.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/view_zone_logic.h" #include "../card_menu_action_type.h" -#include "../player.h" -#include "../player_actions.h" +#include "../player_graphics_item.h" #include "move_menu.h" #include "pt_menu.h" +#include #include #include #include -CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive) +/** + * @brief Creates a circular icon filled with the specified color. + */ +static QIcon createCircleIcon(const QColor &color) +{ + QPixmap pixmap(32, 32); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + painter.setBrush(color); + painter.drawEllipse(pixmap.rect()); + + return QIcon(pixmap); +} + +template +static QAction *makeAction(QObject *parent, Slot &&slot, bool checkable = false, bool checked = false) +{ + auto *a = new QAction(parent); + a->setCheckable(checkable); + if (checkable) { + a->setChecked(checked); + } + QObject::connect(a, &QAction::triggered, parent, std::forward(slot)); + return a; +} + +CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _shortcutsActive) : player(_player), card(_card), shortcutsActive(_shortcutsActive) { - auto playerActions = player->getPlayerActions(); - - const QList &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const QList &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto playerToAdd : players) { - if (playerToAdd == player) { + if (playerToAdd == player->getLogic()) { continue; } playersInfo.append(qMakePair(playerToAdd->getPlayerInfo()->getName(), playerToAdd->getPlayerInfo()->getId())); } - connect(player->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this, &CardMenu::removePlayer); + connect(player->getLogic()->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this, + &CardMenu::removePlayer); - aTap = new QAction(this); - aTap->setData(cmTap); - connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); - aDoesntUntap = new QAction(this); - aDoesntUntap->setData(cmDoesntUntap); - aDoesntUntap->setCheckable(true); - aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap()); - connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); + auto *actions = player->getLogic()->getPlayerActions(); + auto *gameScene = player->getGameScene(); + + // Single selection resolver used by all lambdas — called at trigger time + auto sel = [gameScene]() { return gameScene->selectedCards(); }; + + // Unified dispatcher for card menu actions + auto invoke = [actions, sel](CardMenuActionType type) { + return [actions, sel, type]() { actions->cardMenuAction(sel(), type); }; + }; + + // Actions using invoke (type dispatch, need selection) + aTap = makeAction(this, invoke(cmTap)); + aDoesntUntap = makeAction(this, invoke(cmDoesntUntap), /*checkable=*/true, card && card->getDoesntUntap()); + aFlip = makeAction(this, invoke(cmFlip)); + aPeek = makeAction(this, invoke(cmPeek)); + aClone = makeAction(this, invoke(cmClone)); + + // Actions using selection directly + aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); }); + aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); }); + aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); }); + aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); }); + aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); }); + aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); }); + + // Actions that use activeCard, not selection — direct connection aAttach = new QAction(this); - connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach); - aUnattach = new QAction(this); - connect(aUnattach, &QAction::triggered, playerActions, &PlayerActions::actUnattach); aDrawArrow = new QAction(this); - connect(aDrawArrow, &QAction::triggered, playerActions, &PlayerActions::actDrawArrow); - aSetAnnotation = new QAction(this); - connect(aSetAnnotation, &QAction::triggered, playerActions, &PlayerActions::actSetAnnotation); - aFlip = new QAction(this); - aFlip->setData(cmFlip); - connect(aFlip, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - aPeek = new QAction(this); - aPeek->setData(cmPeek); - connect(aPeek, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - aClone = new QAction(this); - aClone->setData(cmClone); - connect(aClone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); aSelectAll = new QAction(this); - connect(aSelectAll, &QAction::triggered, playerActions, &PlayerActions::actSelectAll); aSelectRow = new QAction(this); - connect(aSelectRow, &QAction::triggered, playerActions, &PlayerActions::actSelectRow); aSelectColumn = new QAction(this); - connect(aSelectColumn, &QAction::triggered, playerActions, &PlayerActions::actSelectColumn); - aPlay = new QAction(this); - connect(aPlay, &QAction::triggered, playerActions, &PlayerActions::actPlay); - aHide = new QAction(this); - connect(aHide, &QAction::triggered, playerActions, &PlayerActions::actHide); - aPlayFacedown = new QAction(this); - connect(aPlayFacedown, &QAction::triggered, playerActions, &PlayerActions::actPlayFacedown); + connect(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach); + connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow); + connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll); + connect(aSelectRow, &QAction::triggered, actions, &PlayerActions::actSelectRow); + connect(aSelectColumn, &QAction::triggered, actions, &PlayerActions::actSelectColumn); aRevealToAll = new QAction(this); mCardCounters = new QMenu; + // Card counters for (int i = 0; i < 6; ++i) { - auto *tempAddCounter = new QAction(this); - tempAddCounter->setData(9 + i * 1000); - auto *tempRemoveCounter = new QAction(this); - tempRemoveCounter->setData(10 + i * 1000); - auto *tempSetCounter = new QAction(this); - tempSetCounter->setData(11 + i * 1000); - aAddCounter.append(tempAddCounter); - aRemoveCounter.append(tempRemoveCounter); - aSetCounter.append(tempSetCounter); - connect(tempAddCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger); - connect(tempRemoveCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger); - connect(tempSetCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger); + QColor color = SettingsCache::instance().cardCounters().color(i); + QIcon circleIcon = createCircleIcon(color); + + auto *addAction = makeAction(this, [actions, sel, i]() { actions->actAddCardCounter(sel(), i); }); + addAction->setIcon(circleIcon); + aAddCounter.append(addAction); + + auto *removeAction = makeAction(this, [actions, sel, i]() { actions->actRemoveCardCounter(sel(), i); }); + removeAction->setIcon(circleIcon); + aRemoveCounter.append(removeAction); + + auto *setAction = makeAction(this, [actions, sel, i]() { actions->actRequestSetCardCounterDialog(sel(), i); }); + setAction->setIcon(circleIcon); + aSetCounter.append(setAction); } setShortcutsActive(); @@ -97,7 +129,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive } bool revealedCard = false; - bool writeableCard = player->getPlayerInfo()->getLocalOrJudge(); + bool writeableCard = player->getLogic()->getPlayerInfo()->getLocalOrJudge(); if (auto *view = qobject_cast(card->getZone())) { if (view->getRevealZone()) { if (view->getWriteableRevealZone()) { @@ -134,7 +166,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive } } -void CardMenu::removePlayer(Player *playerToRemove) +void CardMenu::removePlayer(PlayerLogic *playerToRemove) { for (auto it = playersInfo.begin(); it != playersInfo.end();) { if (it->second == playerToRemove->getPlayerInfo()->getId()) { @@ -153,6 +185,8 @@ void CardMenu::createTableMenu(bool canModifyCard) addSeparator(); addAction(aClone); addSeparator(); + addAction(aReduceLifeByPower); + addSeparator(); addAction(aSelectAll); addAction(aSelectRow); addRelatedCardView(); @@ -179,6 +213,8 @@ void CardMenu::createTableMenu(bool canModifyCard) addMenu(new PtMenu(player)); addAction(aSetAnnotation); addSeparator(); + addAction(aReduceLifeByPower); + addSeparator(); addAction(aSelectAll); addAction(aSelectRow); @@ -277,7 +313,9 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard) initContextualPlayersMenu(revealMenu, aRevealToAll); - connect(revealMenu, &QMenu::triggered, player->getPlayerActions(), &PlayerActions::actReveal); + connect(revealMenu, &QMenu::triggered, this, [this](QAction *action) { + player->getLogic()->getPlayerActions()->actReveal(player->getGameScene()->selectedCards(), action); + }); addSeparator(); addAction(aClone); @@ -362,8 +400,7 @@ void CardMenu::addRelatedCardView() QAction *viewCard = viewRelatedCards->addAction(relatedCardName); Q_UNUSED(viewCard); - connect(viewCard, &QAction::triggered, player->getGame(), - [this, cardRef] { player->getGame()->getTab()->viewCardInfo(cardRef); }); + connect(viewCard, &QAction::triggered, this, [this, cardRef] { emit cardInfoRequested(cardRef); }); } } @@ -425,7 +462,8 @@ void CardMenu::addRelatedCardActions() auto *createRelated = new QAction(text, this); createRelated->setData(QVariant(index++)); - connect(createRelated, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actCreateRelatedCard); + connect(createRelated, &QAction::triggered, player->getLogic()->getPlayerActions(), + &PlayerActions::actCreateRelatedCard); addAction(createRelated); } @@ -434,7 +472,7 @@ void CardMenu::addRelatedCardActions() createRelatedCards->setShortcuts( SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens")); } - connect(createRelatedCards, &QAction::triggered, player->getPlayerActions(), + connect(createRelatedCards, &QAction::triggered, player->getLogic()->getPlayerActions(), &PlayerActions::actCreateAllRelatedCards); addAction(createRelatedCards); } @@ -463,6 +501,7 @@ void CardMenu::retranslateUi() aUnattach->setText(tr("Unattac&h")); aDrawArrow->setText(tr("&Draw arrow...")); aSetAnnotation->setText(tr("&Set annotation...")); + aReduceLifeByPower->setText(tr("Reduce life by power")); mCardCounters->setTitle(tr("Ca&rd counters")); @@ -497,6 +536,7 @@ void CardMenu::setShortcutsActive() aUnattach->setShortcuts(shortcuts.getShortcut("Player/aUnattach")); aDrawArrow->setShortcuts(shortcuts.getShortcut("Player/aDrawArrow")); aSetAnnotation->setShortcuts(shortcuts.getShortcut("Player/aSetAnnotation")); + aReduceLifeByPower->setShortcuts(shortcuts.getShortcut("Player/aReduceLifeByPower")); aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll")); aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow")); diff --git a/cockatrice/src/game/player/menu/card_menu.h b/cockatrice/src/game_graphics/player/menu/card_menu.h similarity index 75% rename from cockatrice/src/game/player/menu/card_menu.h rename to cockatrice/src/game_graphics/player/menu/card_menu.h index b7f2f8241..d67ef3876 100644 --- a/cockatrice/src/game/player/menu/card_menu.h +++ b/cockatrice/src/game_graphics/player/menu/card_menu.h @@ -1,23 +1,28 @@ /** * @file card_menu.h * @ingroup GameMenusCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_CARD_MENU_H #define COCKATRICE_CARD_MENU_H #include +#include class CardItem; -class Player; +class PlayerGraphicsItem; +class PlayerLogic; class CardMenu : public QMenu { Q_OBJECT +signals: + void cardInfoRequested(const CardRef &cardRef); + public: - explicit CardMenu(Player *player, const CardItem *card, bool shortcutsActive); - void removePlayer(Player *playerToRemove); + explicit CardMenu(PlayerGraphicsItem *player, const CardItem *card, bool shortcutsActive); + void removePlayer(PlayerLogic *playerToRemove); void createTableMenu(bool canModifyCard); void createStackMenu(bool canModifyCard); void createGraveyardOrExileMenu(bool canModifyCard); @@ -36,11 +41,12 @@ public: QAction *aFlip, *aPeek; QAction *aAttach, *aUnattach; QAction *aSetAnnotation; + QAction *aReduceLifeByPower; QList aAddCounter, aSetCounter, aRemoveCounter; private: - Player *player; + PlayerGraphicsItem *player; const CardItem *card; QList> playersInfo; bool shortcutsActive; diff --git a/cockatrice/src/game/player/menu/custom_zone_menu.cpp b/cockatrice/src/game_graphics/player/menu/custom_zone_menu.cpp similarity index 63% rename from cockatrice/src/game/player/menu/custom_zone_menu.cpp rename to cockatrice/src/game_graphics/player/menu/custom_zone_menu.cpp index b0f3284c9..743746cc8 100644 --- a/cockatrice/src/game/player/menu/custom_zone_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/custom_zone_menu.cpp @@ -1,13 +1,14 @@ #include "custom_zone_menu.h" -#include "../player.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" -CustomZoneMenu::CustomZoneMenu(Player *_player) : player(_player) +CustomZoneMenu::CustomZoneMenu(PlayerGraphicsItem *_player) : player(_player) { menuAction()->setVisible(false); - connect(player, &Player::clearCustomZonesMenu, this, &CustomZoneMenu::clearCustomZonesMenu); - connect(player, &Player::addViewCustomZoneActionToCustomZoneMenu, this, + connect(player->getLogic(), &PlayerLogic::clearCustomZonesMenu, this, &CustomZoneMenu::clearCustomZonesMenu); + connect(player->getLogic(), &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this, &CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu); retranslateUi(); @@ -17,7 +18,7 @@ void CustomZoneMenu::retranslateUi() { setTitle(tr("C&ustom Zones")); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { for (auto aViewZone : actions()) { aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString())); @@ -37,5 +38,5 @@ void CustomZoneMenu::addViewCustomZoneActionToCustomZoneMenu(QString zoneName) QAction *aViewZone = addAction(tr("View custom zone '%1'").arg(zoneName)); aViewZone->setData(zoneName); connect(aViewZone, &QAction::triggered, this, - [zoneName, this]() { player->getGameScene()->toggleZoneView(player, zoneName, -1); }); + [zoneName, this]() { player->getGameScene()->toggleZoneView(player->getLogic(), zoneName, -1); }); } \ No newline at end of file diff --git a/cockatrice/src/game/player/menu/custom_zone_menu.h b/cockatrice/src/game_graphics/player/menu/custom_zone_menu.h similarity index 80% rename from cockatrice/src/game/player/menu/custom_zone_menu.h rename to cockatrice/src/game_graphics/player/menu/custom_zone_menu.h index c4e66754e..46dd58db6 100644 --- a/cockatrice/src/game/player/menu/custom_zone_menu.h +++ b/cockatrice/src/game_graphics/player/menu/custom_zone_menu.h @@ -1,8 +1,8 @@ /** * @file custom_zone_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_CUSTOM_ZONE_MENU_H #define COCKATRICE_CUSTOM_ZONE_MENU_H @@ -11,12 +11,12 @@ #include -class Player; +class PlayerGraphicsItem; class CustomZoneMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: - explicit CustomZoneMenu(Player *player); + explicit CustomZoneMenu(PlayerGraphicsItem *player); void retranslateUi() override; void setShortcutsActive() override { @@ -26,7 +26,7 @@ public: } private: - Player *player; + PlayerGraphicsItem *player; private slots: void clearCustomZonesMenu(); void addViewCustomZoneActionToCustomZoneMenu(QString zoneName); diff --git a/cockatrice/src/game/player/menu/grave_menu.cpp b/cockatrice/src/game_graphics/player/menu/grave_menu.cpp similarity index 79% rename from cockatrice/src/game/player/menu/grave_menu.cpp rename to cockatrice/src/game_graphics/player/menu/grave_menu.cpp index 2af62c08a..698481f7a 100644 --- a/cockatrice/src/game/player/menu/grave_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/grave_menu.cpp @@ -1,21 +1,22 @@ #include "grave_menu.h" -#include "../../abstract_game.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/abstract_game.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" #include #include #include -GraveyardMenu::GraveyardMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player) +GraveyardMenu::GraveyardMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { createMoveActions(); createViewActions(); addAction(aViewGraveyard); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { mRevealRandomGraveyardCard = addMenu(QString()); connect(mRevealRandomGraveyardCard, &QMenu::aboutToShow, this, &GraveyardMenu::populateRevealRandomMenuWithActivePlayers); @@ -36,9 +37,9 @@ GraveyardMenu::GraveyardMenu(Player *_player, QWidget *parent) : TearOffMenu(par void GraveyardMenu::createMoveActions() { - auto grave = player->getGraveZone(); + auto grave = player->getLogic()->getGraveZone(); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aMoveGraveToTopLibrary = new QAction(this); aMoveGraveToTopLibrary->setData(QList() << ZoneNames::DECK << 0); @@ -60,7 +61,7 @@ void GraveyardMenu::createMoveActions() void GraveyardMenu::createViewActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); aViewGraveyard = new QAction(this); connect(aViewGraveyard, &QAction::triggered, playerActions, &PlayerActions::actViewGraveyard); @@ -76,10 +77,11 @@ void GraveyardMenu::populateRevealRandomMenuWithActivePlayers() mRevealRandomGraveyardCard->addSeparator(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mRevealRandomGraveyardCard->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &GraveyardMenu::onRevealRandomTriggered); @@ -89,7 +91,7 @@ void GraveyardMenu::populateRevealRandomMenuWithActivePlayers() void GraveyardMenu::onRevealRandomTriggered() { if (auto *a = qobject_cast(sender())) { - player->getPlayerActions()->actRevealRandomGraveyardCard(a->data().toInt()); + player->getLogic()->getPlayerActions()->actRevealRandomGraveyardCard(a->data().toInt()); } } @@ -99,7 +101,7 @@ void GraveyardMenu::retranslateUi() aViewGraveyard->setText(tr("&View graveyard")); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { moveGraveMenu->setTitle(tr("&Move graveyard to...")); aMoveGraveToTopLibrary->setText(tr("&Top of library")); aMoveGraveToBottomLibrary->setText(tr("&Bottom of library")); diff --git a/cockatrice/src/game/player/menu/grave_menu.h b/cockatrice/src/game_graphics/player/menu/grave_menu.h similarity index 85% rename from cockatrice/src/game/player/menu/grave_menu.h rename to cockatrice/src/game_graphics/player/menu/grave_menu.h index 429173afa..116261e9b 100644 --- a/cockatrice/src/game/player/menu/grave_menu.h +++ b/cockatrice/src/game_graphics/player/menu/grave_menu.h @@ -1,8 +1,8 @@ /** * @file grave_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_GRAVE_MENU_H #define COCKATRICE_GRAVE_MENU_H @@ -13,7 +13,7 @@ #include #include -class Player; +class PlayerGraphicsItem; class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT @@ -21,7 +21,7 @@ signals: void newPlayerActionCreated(QAction *action); public: - explicit GraveyardMenu(Player *player, QWidget *parent = nullptr); + explicit GraveyardMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr); void createMoveActions(); void createViewActions(); void populateRevealRandomMenuWithActivePlayers(); @@ -40,7 +40,7 @@ public: QAction *aMoveGraveToRfg = nullptr; private: - Player *player; + PlayerGraphicsItem *player; }; #endif // COCKATRICE_GRAVE_MENU_H diff --git a/cockatrice/src/game/player/menu/hand_menu.cpp b/cockatrice/src/game_graphics/player/menu/hand_menu.cpp similarity index 85% rename from cockatrice/src/game/player/menu/hand_menu.cpp rename to cockatrice/src/game_graphics/player/menu/hand_menu.cpp index d65c136bf..ba0702f07 100644 --- a/cockatrice/src/game/player/menu/hand_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/hand_menu.cpp @@ -2,19 +2,23 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../abstract_game.h" -#include "../../zones/hand_zone.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../../game_graphics/zones/hand_zone.h" +#include "../../game/abstract_game.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" #include #include #include -HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : TearOffMenu(parent), player(_player) +HandMenu::HandMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + auto *actions = player->getLogic()->getPlayerActions(); + + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aViewHand = new QAction(this); + connect(aViewHand, &QAction::triggered, actions, &PlayerActions::actViewHand); addAction(aViewHand); @@ -58,7 +62,7 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T addSeparator(); aMulligan = new QAction(this); - connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan); + connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actRequestMulliganDialog); addAction(aMulligan); // Mulligan same size @@ -75,7 +79,7 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T mMoveHandMenu = addTearOffMenu(QString()); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aMoveHandToTopLibrary = new QAction(this); aMoveHandToTopLibrary->setData(QList() << ZoneNames::DECK << 0); aMoveHandToBottomLibrary = new QAction(this); @@ -85,7 +89,7 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T aMoveHandToRfg = new QAction(this); aMoveHandToRfg->setData(QList() << ZoneNames::EXILE << 0); - auto hand = player->getHandZone(); + auto hand = player->getLogic()->getHandZone(); connect(aMoveHandToTopLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone); connect(aMoveHandToBottomLibrary, &QAction::triggered, hand, &HandZoneLogic::moveAllToZone); @@ -107,7 +111,7 @@ void HandMenu::retranslateUi() { setTitle(tr("&Hand")); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aViewHand->setText(tr("&View hand")); mSortHand->setTitle(tr("Sort hand by...")); @@ -166,10 +170,11 @@ void HandMenu::populateRevealHandMenuWithActivePlayers() mRevealHand->addSeparator(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mRevealHand->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &HandMenu::onRevealHandTriggered); @@ -184,10 +189,11 @@ void HandMenu::populateRevealRandomHandCardMenuWithActivePlayers() mRevealRandomHandCard->addSeparator(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mRevealRandomHandCard->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &HandMenu::onRevealRandomHandCardTriggered); @@ -197,19 +203,21 @@ void HandMenu::populateRevealRandomHandCardMenuWithActivePlayers() void HandMenu::onRevealHandTriggered() { auto *action = qobject_cast(sender()); - if (!action) + if (!action) { return; + } const int targetId = action->data().toInt(); - player->getPlayerActions()->actRevealHand(targetId); + player->getLogic()->getPlayerActions()->actRevealHand(targetId); } void HandMenu::onRevealRandomHandCardTriggered() { auto *action = qobject_cast(sender()); - if (!action) + if (!action) { return; + } const int targetId = action->data().toInt(); - player->getPlayerActions()->actRevealRandomHandCard(targetId); + player->getLogic()->getPlayerActions()->actRevealRandomHandCard(targetId); } diff --git a/cockatrice/src/game/player/menu/hand_menu.h b/cockatrice/src/game_graphics/player/menu/hand_menu.h similarity index 91% rename from cockatrice/src/game/player/menu/hand_menu.h rename to cockatrice/src/game_graphics/player/menu/hand_menu.h index 76434cc98..d5204612b 100644 --- a/cockatrice/src/game/player/menu/hand_menu.h +++ b/cockatrice/src/game_graphics/player/menu/hand_menu.h @@ -1,8 +1,8 @@ /** * @file hand_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_HAND_MENU_H #define COCKATRICE_HAND_MENU_H @@ -13,7 +13,7 @@ #include #include -class Player; +class PlayerGraphicsItem; class PlayerActions; class HandMenu : public TearOffMenu, public AbstractPlayerComponent @@ -21,7 +21,7 @@ class HandMenu : public TearOffMenu, public AbstractPlayerComponent Q_OBJECT public: - HandMenu(Player *player, PlayerActions *actions, QWidget *parent = nullptr); + HandMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr); QMenu *revealHandMenu() const { @@ -43,7 +43,7 @@ private slots: void onRevealRandomHandCardTriggered(); private: - Player *player; + PlayerGraphicsItem *player; QAction *aViewHand = nullptr; QAction *aMulligan = nullptr; diff --git a/cockatrice/src/game/player/menu/library_menu.cpp b/cockatrice/src/game_graphics/player/menu/library_menu.cpp similarity index 86% rename from cockatrice/src/game/player/menu/library_menu.cpp rename to cockatrice/src/game_graphics/player/menu/library_menu.cpp index 1bb647d06..4c15e09ec 100644 --- a/cockatrice/src/game/player/menu/library_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/library_menu.cpp @@ -3,14 +3,16 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" #include "../../../interface/widgets/tabs/tab_game.h" -#include "../../abstract_game.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/abstract_game.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" #include +#include #include -LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player) +LibraryMenu::LibraryMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { createDrawActions(); createShuffleActions(); @@ -75,8 +77,8 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent) bottomLibraryMenu->addSeparator(); bottomLibraryMenu->addAction(aShuffleBottomCards); - connect(player, &Player::resetTopCardMenuActions, this, &LibraryMenu::resetTopCardMenuActions); - connect(player, &Player::deckChanged, this, &LibraryMenu::enableOpenInDeckEditorAction); + connect(player->getLogic(), &PlayerLogic::resetTopCardMenuActions, this, &LibraryMenu::resetTopCardMenuActions); + connect(player->getLogic(), &PlayerLogic::deckChanged, this, &LibraryMenu::enableOpenInDeckEditorAction); retranslateUi(); } @@ -94,41 +96,41 @@ void LibraryMenu::resetTopCardMenuActions() void LibraryMenu::createDrawActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aDrawCard = new QAction(this); connect(aDrawCard, &QAction::triggered, playerActions, &PlayerActions::actDrawCard); aDrawCards = new QAction(this); - connect(aDrawCards, &QAction::triggered, playerActions, &PlayerActions::actDrawCards); + connect(aDrawCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawCardsDialog); aUndoDraw = new QAction(this); connect(aUndoDraw, &QAction::triggered, playerActions, &PlayerActions::actUndoDraw); aDrawBottomCard = new QAction(this); connect(aDrawBottomCard, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCard); aDrawBottomCards = new QAction(this); - connect(aDrawBottomCards, &QAction::triggered, playerActions, &PlayerActions::actDrawBottomCards); + connect(aDrawBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestDrawBottomCardsDialog); } } void LibraryMenu::createShuffleActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aShuffle = new QAction(this); connect(aShuffle, &QAction::triggered, playerActions, &PlayerActions::actShuffle); aShuffleTopCards = new QAction(this); - connect(aShuffleTopCards, &QAction::triggered, playerActions, &PlayerActions::actShuffleTop); + connect(aShuffleTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleTopDialog); aShuffleBottomCards = new QAction(this); - connect(aShuffleBottomCards, &QAction::triggered, playerActions, &PlayerActions::actShuffleBottom); + connect(aShuffleBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestShuffleBottomDialog); } } void LibraryMenu::createMoveActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aMoveTopToPlay = new QAction(this); connect(aMoveTopToPlay, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToPlay); aMoveTopToPlayFaceDown = new QAction(this); @@ -149,7 +151,8 @@ void LibraryMenu::createMoveActions() connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToExileFaceDown); aMoveTopCardsUntil = new QAction(this); - connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsUntil); + connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, + &PlayerActions::actRequestMoveTopCardsUntilDialog); aMoveTopCardToBottom = new QAction(this); connect(aMoveTopCardToBottom, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToBottom); @@ -181,16 +184,16 @@ void LibraryMenu::createMoveActions() void LibraryMenu::createViewActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); - if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { + if (player->getLogic()->getPlayerInfo()->local || player->getLogic()->getPlayerInfo()->judge) { aViewLibrary = new QAction(this); connect(aViewLibrary, &QAction::triggered, playerActions, &PlayerActions::actViewLibrary); aViewTopCards = new QAction(this); - connect(aViewTopCards, &QAction::triggered, playerActions, &PlayerActions::actViewTopCards); + connect(aViewTopCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewTopCardsDialog); aViewBottomCards = new QAction(this); - connect(aViewBottomCards, &QAction::triggered, playerActions, &PlayerActions::actViewBottomCards); + connect(aViewBottomCards, &QAction::triggered, playerActions, &PlayerActions::actRequestViewBottomCardsDialog); aAlwaysRevealTopCard = new QAction(this); aAlwaysRevealTopCard->setCheckable(true); connect(aAlwaysRevealTopCard, &QAction::triggered, playerActions, &PlayerActions::actAlwaysRevealTopCard); @@ -207,7 +210,7 @@ void LibraryMenu::retranslateUi() { setTitle(tr("&Library")); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aViewLibrary->setText(tr("&View library")); aViewTopCards->setText(tr("View &top cards of library...")); aViewBottomCards->setText(tr("View bottom cards of library...")); @@ -263,10 +266,11 @@ void LibraryMenu::populateRevealLibraryMenuWithActivePlayers() mRevealLibrary->addSeparator(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mRevealLibrary->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &LibraryMenu::onRevealLibraryTriggered); @@ -277,10 +281,11 @@ void LibraryMenu::populateLendLibraryMenuWithActivePlayers() { mLendLibrary->clear(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mLendLibrary->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &LibraryMenu::onLendLibraryTriggered); @@ -297,10 +302,11 @@ void LibraryMenu::populateRevealTopCardMenuWithActivePlayers() mRevealTopCard->addSeparator(); - const auto &players = player->getGame()->getPlayerManager()->getPlayers().values(); + const auto &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values(); for (auto *other : players) { - if (other == player) + if (other == player->getLogic()) { continue; + } QAction *a = mRevealTopCard->addAction(other->getPlayerInfo()->getName()); a->setData(other->getPlayerInfo()->getId()); connect(a, &QAction::triggered, this, &LibraryMenu::onRevealTopCardTriggered); @@ -310,27 +316,33 @@ void LibraryMenu::populateRevealTopCardMenuWithActivePlayers() void LibraryMenu::onRevealLibraryTriggered() { if (auto *a = qobject_cast(sender())) { - player->getPlayerActions()->actRevealLibrary(a->data().toInt()); + player->getLogic()->getPlayerActions()->actRevealLibrary(a->data().toInt()); } } void LibraryMenu::onLendLibraryTriggered() { if (auto *a = qobject_cast(sender())) { - player->getPlayerActions()->actLendLibrary(a->data().toInt()); + player->getLogic()->getPlayerActions()->actLendLibrary(a->data().toInt()); } } void LibraryMenu::onRevealTopCardTriggered() { + QWidget *parent = nullptr; + if (auto *view = player->scene() ? player->scene()->views().value(0) : nullptr) { + parent = view->window(); + } if (auto *a = qobject_cast(sender())) { - int deckSize = player->getDeckZone()->getCards().size(); - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Reveal top cards of library"), + + int deckSize = player->getLogic()->getDeckZone()->getCards().size(); + bool ok = true; + int number = QInputDialog::getInt(parent, tr("Reveal top cards of library"), tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1, deckSize, 1, &ok); + if (ok) { - player->getPlayerActions()->actRevealTopCards(a->data().toInt(), number); + player->getLogic()->getPlayerActions()->actRevealTopCards(a->data().toInt(), number); defaultNumberTopCards = number; } } diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game_graphics/player/menu/library_menu.h similarity index 94% rename from cockatrice/src/game/player/menu/library_menu.h rename to cockatrice/src/game_graphics/player/menu/library_menu.h index 444e8f516..bc0e6fb8e 100644 --- a/cockatrice/src/game/player/menu/library_menu.h +++ b/cockatrice/src/game_graphics/player/menu/library_menu.h @@ -1,8 +1,8 @@ /** * @file library_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_LIBRARY_MENU_H #define COCKATRICE_LIBRARY_MENU_H @@ -13,7 +13,8 @@ #include #include -class Player; +class PlayerGraphicsItem; +class PlayerLogic; class PlayerActions; class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent @@ -24,7 +25,7 @@ public slots: void resetTopCardMenuActions(); public: - LibraryMenu(Player *player, QWidget *parent = nullptr); + LibraryMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr); void createDrawActions(); void createShuffleActions(); void createMoveActions(); @@ -111,7 +112,7 @@ public: int defaultNumberTopCards = 1; private: - Player *player; + PlayerGraphicsItem *player; }; #endif // COCKATRICE_LIBRARY_MENU_H diff --git a/cockatrice/src/game/player/menu/move_menu.cpp b/cockatrice/src/game_graphics/player/menu/move_menu.cpp similarity index 64% rename from cockatrice/src/game/player/menu/move_menu.cpp rename to cockatrice/src/game_graphics/player/menu/move_menu.cpp index 91e2d8d10..5b7209a9f 100644 --- a/cockatrice/src/game/player/menu/move_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/move_menu.cpp @@ -1,10 +1,11 @@ #include "move_menu.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" #include "../card_menu_action_type.h" -#include "../player.h" -#include "../player_actions.h" +#include "../player_graphics_item.h" -MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) +MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to")) { aMoveToTopLibrary = new QAction(this); aMoveToTopLibrary->setData(cmMoveToTopLibrary); @@ -20,14 +21,22 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) aMoveToExile = new QAction(this); aMoveToExile->setData(cmMoveToExile); - connect(aMoveToTopLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - connect(aMoveToXfromTopOfLibrary, &QAction::triggered, player->getPlayerActions(), - &PlayerActions::actMoveCardXCardsFromTop); - connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); - connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); + auto *actions = player->getLogic()->getPlayerActions(); + + auto invoke = [player](CardMenuActionType type) { + return [type, player]() { + player->getLogic()->getPlayerActions()->cardMenuAction(player->getGameScene()->selectedCards(), type); + }; + }; + + connect(aMoveToTopLibrary, &QAction::triggered, actions, invoke(cmMoveToTopLibrary)); + connect(aMoveToBottomLibrary, &QAction::triggered, actions, invoke(cmMoveToBottomLibrary)); + connect(aMoveToXfromTopOfLibrary, &QAction::triggered, actions, + &PlayerActions::actRequestMoveCardXCardsFromTopDialog); + connect(aMoveToTable, &QAction::triggered, actions, invoke(cmMoveToTable)); + connect(aMoveToHand, &QAction::triggered, actions, invoke(cmMoveToHand)); + connect(aMoveToGraveyard, &QAction::triggered, actions, invoke(cmMoveToGraveyard)); + connect(aMoveToExile, &QAction::triggered, actions, invoke(cmMoveToExile)); addAction(aMoveToTopLibrary); addAction(aMoveToXfromTopOfLibrary); diff --git a/cockatrice/src/game/player/menu/move_menu.h b/cockatrice/src/game_graphics/player/menu/move_menu.h similarity index 84% rename from cockatrice/src/game/player/menu/move_menu.h rename to cockatrice/src/game_graphics/player/menu/move_menu.h index dc39cb6a5..150bdbd3c 100644 --- a/cockatrice/src/game/player/menu/move_menu.h +++ b/cockatrice/src/game_graphics/player/menu/move_menu.h @@ -1,20 +1,20 @@ /** * @file move_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_MOVE_MENU_H #define COCKATRICE_MOVE_MENU_H #include -class Player; +class PlayerGraphicsItem; class MoveMenu : public QMenu { Q_OBJECT public: - explicit MoveMenu(Player *player); + explicit MoveMenu(PlayerGraphicsItem *player); void setShortcutsActive(); void retranslateUi(); diff --git a/cockatrice/src/game_graphics/player/menu/player_menu.cpp b/cockatrice/src/game_graphics/player/menu/player_menu.cpp new file mode 100644 index 000000000..17b791222 --- /dev/null +++ b/cockatrice/src/game_graphics/player/menu/player_menu.cpp @@ -0,0 +1,140 @@ +#include "player_menu.h" + +#include "../../../game_graphics/zones/hand_zone.h" +#include "../../../game_graphics/zones/pile_zone.h" +#include "../../../game_graphics/zones/table_zone.h" +#include "../../../interface/widgets/tabs/tab_game.h" +#include "../../board/card_item.h" +#include "../player_graphics_item.h" +#include "card_menu.h" +#include "hand_menu.h" + +#include + +PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_player) +{ + connect(player->getLogic(), &PlayerLogic::requestCardMenuUpdate, this, &PlayerMenu::updateCardMenu); + connect(this, &PlayerMenu::cardInfoRequested, player, &PlayerGraphicsItem::cardInfoRequested); + + playerMenu = new TearOffMenu(); + + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { + handMenu = addManagedMenu(player, playerMenu); + libraryMenu = addManagedMenu(player, playerMenu); + } else { + handMenu = nullptr; + libraryMenu = nullptr; + } + + graveMenu = addManagedMenu(player, playerMenu); + rfgMenu = addManagedMenu(player, playerMenu); + + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { + sideboardMenu = addManagedMenu(player, playerMenu); + customZonesMenu = addManagedMenu(player); + playerMenu->addSeparator(); + + countersMenu = playerMenu->addMenu(QString()); + + utilityMenu = createManagedComponent(player, playerMenu); + } else { + sideboardMenu = nullptr; + customZonesMenu = nullptr; + countersMenu = nullptr; + utilityMenu = nullptr; + } + + if (player->getLogic()->getPlayerInfo()->getLocal()) { + sayMenu = addManagedMenu(player); + } else { + sayMenu = nullptr; + } + + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &PlayerMenu::refreshShortcuts); + refreshShortcuts(); + + retranslateUi(); +} + +void PlayerMenu::setMenusForGraphicItems() +{ + player->getTableZoneGraphicsItem()->setMenu(playerMenu); + player->getGraveyardZoneGraphicsItem()->setMenu(graveMenu, graveMenu->aViewGraveyard); + player->getRfgZoneGraphicsItem()->setMenu(rfgMenu, rfgMenu->aViewRfg); + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { + player->getHandZoneGraphicsItem()->setMenu(handMenu); + player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard); + player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu); + } +} + +QMenu *PlayerMenu::updateCardMenu(const CardItem *card) +{ + if (!card) { + emit cardMenuUpdated(nullptr); + return nullptr; + } + + // If is spectator (as spectators don't need card menus), return + // only update the menu if the card is actually selected + if ((player->getLogic()->getGame()->getPlayerManager()->isSpectator() && + !player->getLogic()->getGame()->getPlayerManager()->isJudge()) || + player->getLogic()->getGame()->getActiveCard() != card) { + return nullptr; + } + + CardMenu *menu = new CardMenu(player, card, shortcutsActive); + connect(menu, &CardMenu::cardInfoRequested, this, &PlayerMenu::cardInfoRequested); + emit cardMenuUpdated(menu); + + return menu; +} + +void PlayerMenu::retranslateUi() +{ + playerMenu->setTitle(tr("Player \"%1\"").arg(player->getLogic()->getPlayerInfo()->getName())); + + for (auto *component : managedComponents) { + component->retranslateUi(); + } + + if (countersMenu) { + countersMenu->setTitle(tr("&Counters")); + } + + emit retranslateRequested(); +} + +void PlayerMenu::refreshShortcuts() +{ + if (shortcutsActive) { + // Judges get access to every player's menus but only want shortcuts to be set for their own. + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge() && + !player->getLogic()->getPlayerInfo()->getLocal()) { + setShortcutsInactive(); + } else { + setShortcutsActive(); + } + } else { + setShortcutsInactive(); + } +} + +void PlayerMenu::setShortcutsActive() +{ + shortcutsActive = true; + for (auto *c : managedComponents) { + c->setShortcutsActive(); + } + emit shortcutsActivated(); +} + +void PlayerMenu::setShortcutsInactive() +{ + shortcutsActive = false; + for (auto *c : managedComponents) { + c->setShortcutsInactive(); + } + emit shortcutsDeactivated(); +} \ No newline at end of file diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game_graphics/player/menu/player_menu.h similarity index 70% rename from cockatrice/src/game/player/menu/player_menu.h rename to cockatrice/src/game_graphics/player/menu/player_menu.h index 104c5a930..62ba66df7 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game_graphics/player/menu/player_menu.h @@ -8,7 +8,6 @@ #define COCKATRICE_PLAYER_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" -#include "../player.h" #include "custom_zone_menu.h" #include "grave_menu.h" #include "hand_menu.h" @@ -23,26 +22,31 @@ #include class CardItem; +class CardMenu; +class PlayerGraphicsItem; class PlayerMenu : public QObject { Q_OBJECT signals: - void cardMenuUpdated(QMenu *cardMenu); + void cardMenuUpdated(CardMenu *cardMenu); + void cardInfoRequested(const CardRef &cardRef); + void shortcutsActivated(); + void shortcutsDeactivated(); + void retranslateRequested(); public slots: void setMenusForGraphicItems(); + QMenu *updateCardMenu(const CardItem *card); private slots: void refreshShortcuts(); public: - explicit PlayerMenu(Player *player); - /// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters(). + explicit PlayerMenu(PlayerGraphicsItem *player); + /** @brief Retranslate all user-visible strings. Called on language change. */ void retranslateUi(); - QMenu *updateCardMenu(const CardItem *card); - [[nodiscard]] QMenu *getPlayerMenu() const { return playerMenu; @@ -68,13 +72,13 @@ public: return shortcutsActive; } - /// Delegates to all managedComponents, plus counters separately. + /** @brief Bind keyboard shortcuts. Called when this player gains focus. */ void setShortcutsActive(); - /// Delegates to all managedComponents, plus counters separately. + /** @brief Unbind keyboard shortcuts. Called when this player loses focus. */ void setShortcutsInactive(); private: - Player *player; + PlayerGraphicsItem *player; TearOffMenu *playerMenu; QMenu *countersMenu; HandMenu *handMenu; @@ -86,11 +90,13 @@ private: SayMenu *sayMenu; CustomZoneMenu *customZonesMenu; - /// Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via player->getCounters(). + /** @brief Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via + * player->getCounters(). + */ QList managedComponents; bool shortcutsActive = false; - /// Creates component, adds it as a submenu of playerMenu, and registers in managedComponents. + /** @brief Creates component, adds it as a submenu of playerMenu, and registers in managedComponents. */ template MenuT *addManagedMenu(Args &&...args) { auto *menu = new MenuT(std::forward(args)...); @@ -99,7 +105,7 @@ private: return menu; } - /// Creates component and registers in managedComponents, but does NOT add it as a submenu. + /** @brief Creates component and registers in managedComponents, but does NOT add it as a submenu. */ template ComponentT *createManagedComponent(Args &&...args) { auto *component = new ComponentT(std::forward(args)...); diff --git a/cockatrice/src/game/player/menu/pt_menu.cpp b/cockatrice/src/game_graphics/player/menu/pt_menu.cpp similarity index 51% rename from cockatrice/src/game/player/menu/pt_menu.cpp rename to cockatrice/src/game_graphics/player/menu/pt_menu.cpp index dae03e07f..a01be9424 100644 --- a/cockatrice/src/game/player/menu/pt_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/pt_menu.cpp @@ -1,32 +1,43 @@ #include "pt_menu.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" -PtMenu::PtMenu(Player *player) : QMenu(tr("Power / toughness")) +PtMenu::PtMenu(PlayerGraphicsItem *player) : QMenu(tr("Power / toughness")) { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); aIncP = new QAction(this); - connect(aIncP, &QAction::triggered, playerActions, &PlayerActions::actIncP); + connect(aIncP, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actIncP(player->getGameScene()->selectedCards()); }); aDecP = new QAction(this); - connect(aDecP, &QAction::triggered, playerActions, &PlayerActions::actDecP); + connect(aDecP, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actDecP(player->getGameScene()->selectedCards()); }); aIncT = new QAction(this); - connect(aIncT, &QAction::triggered, playerActions, &PlayerActions::actIncT); + connect(aIncT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actIncT(player->getGameScene()->selectedCards()); }); aDecT = new QAction(this); - connect(aDecT, &QAction::triggered, playerActions, &PlayerActions::actDecT); + connect(aDecT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actDecT(player->getGameScene()->selectedCards()); }); aIncPT = new QAction(this); - connect(aIncPT, &QAction::triggered, playerActions, [playerActions] { playerActions->actIncPT(); }); + connect(aIncPT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actIncPT(player->getGameScene()->selectedCards()); }); aDecPT = new QAction(this); - connect(aDecPT, &QAction::triggered, playerActions, &PlayerActions::actDecPT); + connect(aDecPT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actDecPT(player->getGameScene()->selectedCards()); }); aFlowP = new QAction(this); - connect(aFlowP, &QAction::triggered, playerActions, &PlayerActions::actFlowP); + connect(aFlowP, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actFlowP(player->getGameScene()->selectedCards()); }); aFlowT = new QAction(this); - connect(aFlowT, &QAction::triggered, playerActions, &PlayerActions::actFlowT); + connect(aFlowT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actFlowT(player->getGameScene()->selectedCards()); }); aSetPT = new QAction(this); - connect(aSetPT, &QAction::triggered, playerActions, &PlayerActions::actSetPT); + connect(aSetPT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actRequestSetPTDialog(player->getGameScene()->selectedCards()); }); aResetPT = new QAction(this); - connect(aResetPT, &QAction::triggered, playerActions, &PlayerActions::actResetPT); + connect(aResetPT, &QAction::triggered, playerActions, + [player, playerActions] { playerActions->actResetPT(player->getGameScene()->selectedCards()); }); addAction(aIncP); addAction(aDecP); diff --git a/cockatrice/src/game/player/menu/pt_menu.h b/cockatrice/src/game_graphics/player/menu/pt_menu.h similarity index 84% rename from cockatrice/src/game/player/menu/pt_menu.h rename to cockatrice/src/game_graphics/player/menu/pt_menu.h index 44112e54f..72f828801 100644 --- a/cockatrice/src/game/player/menu/pt_menu.h +++ b/cockatrice/src/game_graphics/player/menu/pt_menu.h @@ -1,21 +1,21 @@ /** * @file pt_menu.h * @ingroup GameMenusCards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PT_MENU_H #define COCKATRICE_PT_MENU_H #include -class Player; +class PlayerGraphicsItem; class PtMenu : public QMenu { Q_OBJECT public: - explicit PtMenu(Player *player); + explicit PtMenu(PlayerGraphicsItem *player); void retranslateUi(); void setShortcutsActive(); diff --git a/cockatrice/src/game/player/menu/rfg_menu.cpp b/cockatrice/src/game_graphics/player/menu/rfg_menu.cpp similarity index 78% rename from cockatrice/src/game/player/menu/rfg_menu.cpp rename to cockatrice/src/game_graphics/player/menu/rfg_menu.cpp index 8a101c04c..45abadbf7 100644 --- a/cockatrice/src/game/player/menu/rfg_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/rfg_menu.cpp @@ -1,18 +1,19 @@ #include "rfg_menu.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" #include -RfgMenu::RfgMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player) +RfgMenu::RfgMenu(PlayerGraphicsItem *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { createMoveActions(); createViewActions(); addAction(aViewRfg); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { addSeparator(); moveRfgMenu = addTearOffMenu(QString()); moveRfgMenu->addAction(aMoveRfgToTopLibrary); @@ -28,8 +29,8 @@ RfgMenu::RfgMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player void RfgMenu::createMoveActions() { - if (player->getPlayerInfo()->getLocalOrJudge()) { - auto rfg = player->getRfgZone(); + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { + auto rfg = player->getLogic()->getRfgZone(); aMoveRfgToTopLibrary = new QAction(this); aMoveRfgToTopLibrary->setData(QList() << ZoneNames::DECK << 0); @@ -49,7 +50,7 @@ void RfgMenu::createMoveActions() void RfgMenu::createViewActions() { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); aViewRfg = new QAction(this); connect(aViewRfg, &QAction::triggered, playerActions, &PlayerActions::actViewRfg); @@ -61,7 +62,7 @@ void RfgMenu::retranslateUi() aViewRfg->setText(tr("&View exile")); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { moveRfgMenu->setTitle(tr("&Move exile to...")); aMoveRfgToTopLibrary->setText(tr("&Top of library")); aMoveRfgToBottomLibrary->setText(tr("&Bottom of library")); diff --git a/cockatrice/src/game/player/menu/rfg_menu.h b/cockatrice/src/game_graphics/player/menu/rfg_menu.h similarity index 83% rename from cockatrice/src/game/player/menu/rfg_menu.h rename to cockatrice/src/game_graphics/player/menu/rfg_menu.h index 8f79b2f4a..f5dd888e4 100644 --- a/cockatrice/src/game/player/menu/rfg_menu.h +++ b/cockatrice/src/game_graphics/player/menu/rfg_menu.h @@ -1,8 +1,8 @@ /** * @file rfg_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_RFG_MENU_H #define COCKATRICE_RFG_MENU_H @@ -13,12 +13,12 @@ #include #include -class Player; +class PlayerGraphicsItem; class RfgMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT public: - explicit RfgMenu(Player *player, QWidget *parent = nullptr); + explicit RfgMenu(PlayerGraphicsItem *player, QWidget *parent = nullptr); void createMoveActions(); void createViewActions(); void retranslateUi() override; @@ -38,7 +38,7 @@ public: QAction *aMoveRfgToGrave = nullptr; private: - Player *player; + PlayerGraphicsItem *player; }; #endif // COCKATRICE_RFG_MENU_H diff --git a/cockatrice/src/game/player/menu/say_menu.cpp b/cockatrice/src/game_graphics/player/menu/say_menu.cpp similarity index 78% rename from cockatrice/src/game/player/menu/say_menu.cpp rename to cockatrice/src/game_graphics/player/menu/say_menu.cpp index 116fba49a..336b70f0d 100644 --- a/cockatrice/src/game/player/menu/say_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/say_menu.cpp @@ -1,10 +1,11 @@ #include "say_menu.h" #include "../../../client/settings/cache_settings.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" -SayMenu::SayMenu(Player *_player) : player(_player) +SayMenu::SayMenu(PlayerGraphicsItem *_player) : player(_player) { connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu); initSayMenu(); @@ -44,7 +45,7 @@ void SayMenu::initSayMenu() for (int i = 0; i < count; ++i) { auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this); - connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage); + connect(newAction, &QAction::triggered, player->getLogic()->getPlayerActions(), &PlayerActions::actSayMessage); addAction(newAction); } diff --git a/cockatrice/src/game/player/menu/say_menu.h b/cockatrice/src/game_graphics/player/menu/say_menu.h similarity index 78% rename from cockatrice/src/game/player/menu/say_menu.h rename to cockatrice/src/game_graphics/player/menu/say_menu.h index fadf5f368..3ff160d05 100644 --- a/cockatrice/src/game/player/menu/say_menu.h +++ b/cockatrice/src/game_graphics/player/menu/say_menu.h @@ -1,8 +1,8 @@ /** * @file say_menu.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_SAY_MENU_H #define COCKATRICE_SAY_MENU_H @@ -11,12 +11,12 @@ #include -class Player; +class PlayerGraphicsItem; class SayMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: - explicit SayMenu(Player *player); + explicit SayMenu(PlayerGraphicsItem *player); void retranslateUi() override; void setShortcutsActive() override; @@ -26,7 +26,7 @@ private slots: void initSayMenu(); private: - Player *player; + PlayerGraphicsItem *player; bool shortcutsActive = false; }; diff --git a/cockatrice/src/game/player/menu/sideboard_menu.cpp b/cockatrice/src/game_graphics/player/menu/sideboard_menu.cpp similarity index 56% rename from cockatrice/src/game/player/menu/sideboard_menu.cpp rename to cockatrice/src/game_graphics/player/menu/sideboard_menu.cpp index beaabfcc6..0dd7894d2 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/sideboard_menu.cpp @@ -1,14 +1,16 @@ #include "sideboard_menu.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" -SideboardMenu::SideboardMenu(Player *player, QMenu *playerMenu) : QMenu(playerMenu) +SideboardMenu::SideboardMenu(PlayerGraphicsItem *player, QMenu *playerMenu) : QMenu(playerMenu) { aViewSideboard = new QAction(this); - connect(aViewSideboard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actViewSideboard); + connect(aViewSideboard, &QAction::triggered, player->getLogic()->getPlayerActions(), + &PlayerActions::actViewSideboard); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { addAction(aViewSideboard); } diff --git a/cockatrice/src/game/player/menu/sideboard_menu.h b/cockatrice/src/game_graphics/player/menu/sideboard_menu.h similarity index 74% rename from cockatrice/src/game/player/menu/sideboard_menu.h rename to cockatrice/src/game_graphics/player/menu/sideboard_menu.h index 4a77d1b52..b3b547291 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.h +++ b/cockatrice/src/game_graphics/player/menu/sideboard_menu.h @@ -1,8 +1,8 @@ /** * @file sideboard_menu.h * @ingroup GameMenusZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_SIDEBOARD_MENU_H #define COCKATRICE_SIDEBOARD_MENU_H @@ -11,19 +11,19 @@ #include -class Player; +class PlayerGraphicsItem; class SideboardMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: - explicit SideboardMenu(Player *player, QMenu *playerMenu); + explicit SideboardMenu(PlayerGraphicsItem *player, QMenu *playerMenu); void retranslateUi() override; void setShortcutsActive() override; void setShortcutsInactive() override; private: - Player *player; + PlayerGraphicsItem *player; QAction *aViewSideboard; }; diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game_graphics/player/menu/utility_menu.cpp similarity index 64% rename from cockatrice/src/game/player/menu/utility_menu.cpp rename to cockatrice/src/game_graphics/player/menu/utility_menu.cpp index 37bdcbbaf..61a822b21 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/utility_menu.cpp @@ -1,43 +1,56 @@ #include "utility_menu.h" #include "../../../interface/deck_loader/deck_loader.h" -#include "../player.h" -#include "../player_actions.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../player_graphics_item.h" #include "player_menu.h" #include #include -UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) +UtilityMenu::UtilityMenu(PlayerGraphicsItem *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) { - PlayerActions *playerActions = player->getPlayerActions(); + PlayerActions *playerActions = player->getLogic()->getPlayerActions(); + connect(playerActions, &PlayerActions::requestEnableAndSetCreateAnotherTokenAction, this, + &UtilityMenu::setAndEnableCreateAnotherTokenAction); + connect(playerActions, &PlayerActions::requestSetLastToken, this, &UtilityMenu::setLastToken); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aUntapAll = new QAction(this); connect(aUntapAll, &QAction::triggered, playerActions, &PlayerActions::actUntapAll); aRollDie = new QAction(this); - connect(aRollDie, &QAction::triggered, playerActions, &PlayerActions::actRollDie); + connect(aRollDie, &QAction::triggered, playerActions, &PlayerActions::actRequestRollDieDialog); + + aFlipCoin = new QAction(this); + connect(aFlipCoin, &QAction::triggered, playerActions, &PlayerActions::actFlipCoin); aCreateToken = new QAction(this); - connect(aCreateToken, &QAction::triggered, playerActions, &PlayerActions::actCreateToken); + connect(aCreateToken, &QAction::triggered, playerActions, [this]() { + player->getLogic()->getPlayerActions()->actRequestCreateTokenDialog(getPredefinedTokens()); + }); aCreateAnotherToken = new QAction(this); connect(aCreateAnotherToken, &QAction::triggered, playerActions, &PlayerActions::actCreateAnotherToken); aCreateAnotherToken->setEnabled(false); aIncrementAllCardCounters = new QAction(this); - connect(aIncrementAllCardCounters, &QAction::triggered, player, &Player::incrementAllCardCounters); + connect(aIncrementAllCardCounters, &QAction::triggered, playerActions, [this]() { + player->getLogic()->getPlayerActions()->actIncrementAllCardCounters( + player->getGameScene()->selectedCards()); + }); createPredefinedTokenMenu = new QMenu(QString()); createPredefinedTokenMenu->setEnabled(false); - connect(player, &Player::deckChanged, this, &UtilityMenu::populatePredefinedTokensMenu); + connect(player->getLogic(), &PlayerLogic::deckChanged, this, &UtilityMenu::populatePredefinedTokensMenu); playerMenu->addAction(aIncrementAllCardCounters); playerMenu->addSeparator(); playerMenu->addAction(aUntapAll); playerMenu->addSeparator(); playerMenu->addAction(aRollDie); + playerMenu->addAction(aFlipCoin); playerMenu->addSeparator(); playerMenu->addAction(aCreateToken); playerMenu->addAction(aCreateAnotherToken); @@ -50,6 +63,7 @@ UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu) aIncrementAllCardCounters = nullptr; aUntapAll = nullptr; aRollDie = nullptr; + aFlipCoin = nullptr; } retranslateUi(); @@ -60,7 +74,7 @@ void UtilityMenu::populatePredefinedTokensMenu() clear(); setEnabled(false); predefinedTokens.clear(); - const DeckList &deckList = player->getDeck(); + const DeckList &deckList = player->getLogic()->getDeck(); if (deckList.isEmpty()) { return; @@ -78,17 +92,28 @@ void UtilityMenu::populatePredefinedTokensMenu() if (i < 10) { a->setShortcut(QKeySequence("Alt+" + QString::number((i + 1) % 10))); } - connect(a, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actCreatePredefinedToken); + connect(a, &QAction::triggered, player->getLogic()->getPlayerActions(), + &PlayerActions::actCreatePredefinedToken); } } } +void UtilityMenu::setLastToken(CardInfoPtr lastToken) +{ + if (!createAnotherTokenActionExists()) { + return; + } + + player->getLogic()->getPlayerActions()->setLastTokenInfo(lastToken); +} + void UtilityMenu::retranslateUi() { - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aIncrementAllCardCounters->setText(tr("Increment all card counters")); aUntapAll->setText(tr("&Untap all permanents")); aRollDie->setText(tr("R&oll die...")); + aFlipCoin->setText(tr("Flip coin")); aCreateToken->setText(tr("&Create token...")); aCreateAnotherToken->setText(tr("C&reate another token")); createPredefinedTokenMenu->setTitle(tr("Cr&eate predefined token")); @@ -99,10 +124,11 @@ void UtilityMenu::setShortcutsActive() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aIncrementAllCardCounters->setShortcuts(shortcuts.getShortcut("Player/aIncrementAllCardCounters")); aUntapAll->setShortcuts(shortcuts.getShortcut("Player/aUntapAll")); aRollDie->setShortcuts(shortcuts.getShortcut("Player/aRollDie")); + aFlipCoin->setShortcuts(shortcuts.getShortcut("Player/aFlipCoin")); aCreateToken->setShortcuts(shortcuts.getShortcut("Player/aCreateToken")); aCreateAnotherToken->setShortcuts(shortcuts.getShortcut("Player/aCreateAnotherToken")); } @@ -110,9 +136,10 @@ void UtilityMenu::setShortcutsActive() void UtilityMenu::setShortcutsInactive() { - if (player->getPlayerInfo()->getLocalOrJudge()) { + if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) { aUntapAll->setShortcut(QKeySequence()); aRollDie->setShortcut(QKeySequence()); + aFlipCoin->setShortcut(QKeySequence()); aCreateToken->setShortcut(QKeySequence()); aCreateAnotherToken->setShortcut(QKeySequence()); aIncrementAllCardCounters->setShortcut(QKeySequence()); diff --git a/cockatrice/src/game/player/menu/utility_menu.h b/cockatrice/src/game_graphics/player/menu/utility_menu.h similarity index 72% rename from cockatrice/src/game/player/menu/utility_menu.h rename to cockatrice/src/game_graphics/player/menu/utility_menu.h index f6577d7d1..bdc2a81a5 100644 --- a/cockatrice/src/game/player/menu/utility_menu.h +++ b/cockatrice/src/game_graphics/player/menu/utility_menu.h @@ -1,8 +1,8 @@ /** * @file utility_menu.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_UTILITY_MENU_H #define COCKATRICE_UTILITY_MENU_H @@ -10,19 +10,21 @@ #include "abstract_player_component.h" #include +#include -class Player; +class PlayerGraphicsItem; class UtilityMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public slots: void populatePredefinedTokensMenu(); + void setLastToken(CardInfoPtr lastToken); void retranslateUi() override; void setShortcutsActive() override; void setShortcutsInactive() override; public: - explicit UtilityMenu(Player *player, QMenu *playerMenu); + explicit UtilityMenu(PlayerGraphicsItem *player, QMenu *playerMenu); [[nodiscard]] bool createAnotherTokenActionExists() const { @@ -31,7 +33,7 @@ public: void setAndEnableCreateAnotherTokenAction(QString text) { - aCreateAnotherToken->setText(text); + aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(text)); aCreateAnotherToken->setEnabled(true); } @@ -41,13 +43,13 @@ public: } private: - Player *player; + PlayerGraphicsItem *player; QStringList predefinedTokens; QMenu *createPredefinedTokenMenu; QAction *aIncrementAllCardCounters; - QAction *aUntapAll, *aRollDie; + QAction *aUntapAll, *aRollDie, *aFlipCoin; QAction *aCreateToken, *aCreateAnotherToken; }; diff --git a/cockatrice/src/game/player/player_area.cpp b/cockatrice/src/game_graphics/player/player_area.cpp similarity index 100% rename from cockatrice/src/game/player/player_area.cpp rename to cockatrice/src/game_graphics/player/player_area.cpp diff --git a/cockatrice/src/game/player/player_area.h b/cockatrice/src/game_graphics/player/player_area.h similarity index 91% rename from cockatrice/src/game/player/player_area.h rename to cockatrice/src/game_graphics/player/player_area.h index ec0d23cb6..d73547f81 100644 --- a/cockatrice/src/game/player/player_area.h +++ b/cockatrice/src/game_graphics/player/player_area.h @@ -1,13 +1,13 @@ /** * @file player_area.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_AREA_H #define COCKATRICE_PLAYER_AREA_H -#include "../../game_graphics/board/graphics_item_type.h" +#include "../board/graphics_item_type.h" #include "QGraphicsItem" /** diff --git a/cockatrice/src/game_graphics/player/player_dialogs.cpp b/cockatrice/src/game_graphics/player/player_dialogs.cpp new file mode 100644 index 000000000..3c26ae1fe --- /dev/null +++ b/cockatrice/src/game_graphics/player/player_dialogs.cpp @@ -0,0 +1,298 @@ +#include "player_dialogs.h" + +#include "../../client/settings/card_counter_settings.h" +#include "../../interface/widgets/utility/get_text_with_max.h" +#include "../board/card_item.h" +#include "../dialogs/dlg_roll_dice.h" +#include "../player/player_graphics_item.h" + +#include +#include + +PlayerDialogs::PlayerDialogs(PlayerGraphicsItem *_player, PlayerActions *_playerActions) + : QObject(_player), player(_player), playerActions(_playerActions) +{ + connect(playerActions, &PlayerActions::requestViewTopCardsDialog, this, + &PlayerDialogs::onViewTopCardsDialogRequested); + + connect(playerActions, &PlayerActions::requestViewBottomCardsDialog, this, + &PlayerDialogs::onViewBottomCardsDialogRequested); + + connect(playerActions, &PlayerActions::requestShuffleTopDialog, this, &PlayerDialogs::onShuffleTopDialogRequested); + + connect(playerActions, &PlayerActions::requestShuffleBottomDialog, this, + &PlayerDialogs::onShuffleBottomDialogRequested); + + connect(playerActions, &PlayerActions::requestMulliganDialog, this, &PlayerDialogs::onMulliganDialogRequested); + + connect(playerActions, &PlayerActions::requestDrawCardsDialog, this, &PlayerDialogs::onDrawCardsDialogRequested); + + connect(playerActions, &PlayerActions::requestMoveTopCardsToDialog, this, + &PlayerDialogs::onMoveTopCardsToDialogRequested); + + connect(playerActions, &PlayerActions::requestMoveTopCardsUntilDialog, this, + &PlayerDialogs::onMoveTopCardsUntilDialogRequested); + + connect(playerActions, &PlayerActions::requestMoveBottomCardsToDialog, this, + &PlayerDialogs::onMoveBottomCardsToDialogRequested); + + connect(playerActions, &PlayerActions::requestDrawBottomCardsDialog, this, + &PlayerDialogs::onDrawBottomCardsDialogRequested); + + connect(playerActions, &PlayerActions::requestRollDieDialog, this, &PlayerDialogs::onRollDieDialogRequested); + + connect(playerActions, &PlayerActions::requestCreateTokenDialog, this, + &PlayerDialogs::onCreateTokenDialogRequested); + + connect(playerActions, &PlayerActions::requestCreateRelatedFromRelationDialog, this, + &PlayerDialogs::onCreateRelatedFromRelationDialogRequested); + + connect(playerActions, &PlayerActions::requestMoveCardXCardsFromTopDialog, this, + &PlayerDialogs::onMoveCardXCardsFromTopDialogRequested); + + connect(playerActions, &PlayerActions::requestSetPTDialog, this, &PlayerDialogs::onSetPTDialogRequested); + + connect(playerActions, &PlayerActions::requestSetAnnotationDialog, this, + &PlayerDialogs::onSetAnnotationDialogRequested); + + connect(playerActions, &PlayerActions::requestSetCardCounterDialog, this, + &PlayerDialogs::onSetCardCounterDialogRequested); +} + +void PlayerDialogs::onViewTopCardsDialogRequested(int defaultNumberTopCards, int deckSize) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("View top cards of library"), + tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberTopCards, 1, + deckSize, 1, &ok); + if (ok) { + playerActions->actViewTopCards(number); + } +} + +void PlayerDialogs::onViewBottomCardsDialogRequested(int defaultNumberBottomCards, int deckSize) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("View bottom cards of library"), + tr("Number of cards: (max. %1)").arg(deckSize), defaultNumberBottomCards, 1, + deckSize, 1, &ok); + if (ok) { + playerActions->actViewBottomCards(number); + } +} + +void PlayerDialogs::onShuffleTopDialogRequested(int defaultNumberTopCards, int maxCards) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Shuffle top cards of library"), + tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, + maxCards, 1, &ok); + if (ok) { + playerActions->actShuffleTop(number); + } +} + +void PlayerDialogs::onShuffleBottomDialogRequested(int defaultNumberBottomCards, int maxCards) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Shuffle bottom cards of library"), + tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, + maxCards, 1, &ok); + if (ok) { + playerActions->actShuffleBottom(number); + } +} + +void PlayerDialogs::onMulliganDialogRequested(int startSize, int handSize, int deckSize) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Draw hand"), + tr("Number of cards: (max. %1)").arg(deckSize) + '\n' + + tr("0 and lower are in comparison to current hand size"), + startSize, -handSize, deckSize, 1, &ok); + + if (ok) { + playerActions->actMulligan(number); + } +} + +void PlayerDialogs::onDrawCardsDialogRequested(int defaultNumberTopCards, int deckSize) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Draw cards"), tr("Number of cards: (max. %1)").arg(deckSize), + defaultNumberTopCards, 1, deckSize, 1, &ok); + + if (ok) { + playerActions->actDrawCards(number); + } +} + +void PlayerDialogs::onMoveTopCardsToDialogRequested(int defaultNumberTopCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Move top cards to %1").arg(zoneDisplayName), + tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, + maxCards, 1, &ok); + if (ok) { + playerActions->moveTopCardsTo(number, targetZone, faceDown); + } +} + +void PlayerDialogs::onMoveTopCardsUntilDialogRequested(MoveTopCardsUntilOptions options) +{ + DlgMoveTopCardsUntil dlg(dialogParent(), options); + if (!dlg.exec()) { + return; + } + playerActions->moveTopCardsUntil(dlg.getExpr(), dlg.getOptions()); +} + +void PlayerDialogs::onMoveBottomCardsToDialogRequested(int defaultNumberBottomCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown) +{ + bool ok; + int number = QInputDialog::getInt(dialogParent(), tr("Move bottom cards to %1").arg(zoneDisplayName), + tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, + maxCards, 1, &ok); + if (ok) { + playerActions->moveBottomCardsTo(number, targetZone, faceDown); + } +} + +void PlayerDialogs::onDrawBottomCardsDialogRequested(int defaultNumberBottomCards, int maxCards) +{ + bool ok; + int number = + QInputDialog::getInt(dialogParent(), tr("Draw bottom cards"), tr("Number of cards: (max. %1)").arg(maxCards), + defaultNumberBottomCards, 1, maxCards, 1, &ok); + if (ok) { + playerActions->actDrawBottomCards(number); + } +} + +void PlayerDialogs::onRollDieDialogRequested() +{ + DlgRollDice dlg(dialogParent()); + if (!dlg.exec()) { + return; + } + playerActions->actRollDie(dlg.getDieSideCount(), dlg.getDiceToRollCount()); +} + +void PlayerDialogs::onCreateRelatedFromRelationDialogRequested(const CardItem *sourceCard, + const CardRelation *cardRelation) +{ + if (sourceCard == nullptr || cardRelation == nullptr) { + playerActions->setLastRelatedCreationSucceeded(false); + return; + } + + int variableCount = cardRelation->getDefaultCount(); + + if (cardRelation->getIsVariable()) { + bool ok; + + emit requestDialogSemaphore(true); + + variableCount = QInputDialog::getInt(dialogParent(), tr("Create tokens"), tr("Number:"), + cardRelation->getDefaultCount(), 1, MAX_TOKENS_PER_DIALOG, 1, &ok); + + emit requestDialogSemaphore(false); + + if (!ok) { + playerActions->setLastRelatedCreationSucceeded(false); // cancelled + return; + } + } + + const bool succeeded = playerActions->createRelatedFromRelation(sourceCard, cardRelation, variableCount); + + playerActions->setLastRelatedCreationSucceeded(succeeded); + + if (succeeded) { + playerActions->onRelatedCardCreated(sourceCard, cardRelation); // only on confirmed success + } +} + +void PlayerDialogs::onCreateTokenDialogRequested(const QStringList &predefinedTokens) +{ + DlgCreateToken dlg(predefinedTokens, dialogParent()); + if (!dlg.exec()) { + return; + } + + playerActions->actCreateToken(dlg.getTokenInfo()); +} + +void PlayerDialogs::onMoveCardXCardsFromTopDialogRequested(int defaultNumberTopCardsToPlaceBelow, int deckSize) +{ + bool ok; + int number = + QInputDialog::getInt(dialogParent(), tr("Place card X cards from top of library"), + tr("Which position should this card be placed:") + "\n" + tr("(max. %1)").arg(deckSize), + defaultNumberTopCardsToPlaceBelow, 1, deckSize, 1, &ok); + number -= 1; // indexes start at 0 + + if (ok) { + playerActions->actMoveCardXCardsFromTop(player->getGameScene()->selectedCards(), number); + } +} + +void PlayerDialogs::onSetPTDialogRequested(const QString &oldPT) +{ + bool ok; + auto cards = player->getGameScene()->selectedCards(); + emit requestDialogSemaphore(true); + QString pt = getTextWithMax(dialogParent(), tr("Change power/toughness"), tr("Change stats to:"), QLineEdit::Normal, + oldPT, &ok); + emit requestDialogSemaphore(false); + + if (!ok || player->getLogic()->clearCardsToDelete()) { + return; + } + + playerActions->actSetPT(cards, pt); +} + +void PlayerDialogs::onSetAnnotationDialogRequested(const QString &oldAnnotation) +{ + auto cards = player->getGameScene()->selectedCards(); + emit requestDialogSemaphore(true); + AnnotationDialog *dialog = new AnnotationDialog(dialogParent()); + dialog->setOptions(QInputDialog::UsePlainTextEditForTextInput); + dialog->setWindowTitle(tr("Set annotation")); + dialog->setLabelText(tr("Please enter the new annotation:")); + dialog->setTextValue(oldAnnotation); + bool ok = dialog->exec(); + emit requestDialogSemaphore(false); + if (!ok || player->getLogic()->clearCardsToDelete()) { + return; + } + QString annotation = dialog->textValue().left(MAX_NAME_LENGTH); + playerActions->actSetAnnotation(cards, annotation); +} + +void PlayerDialogs::onSetCardCounterDialogRequested(int counterId, const QString &oldValueForDlg) +{ + auto cards = player->getGameScene()->selectedCards(); + emit requestDialogSemaphore(true); + + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + QString counterName = cardCounterSettings.displayName(counterId); + + AbstractCounterDialog dialog(counterName, oldValueForDlg, dialogParent()); + int ok = dialog.exec(); + + emit requestDialogSemaphore(false); + if (!ok || player->getLogic()->clearCardsToDelete()) { + return; + } + playerActions->actSetCardCounter(cards, counterId, dialog.textValue()); +} \ No newline at end of file diff --git a/cockatrice/src/game_graphics/player/player_dialogs.h b/cockatrice/src/game_graphics/player/player_dialogs.h new file mode 100644 index 000000000..f87704f2d --- /dev/null +++ b/cockatrice/src/game_graphics/player/player_dialogs.h @@ -0,0 +1,63 @@ +#ifndef COCKATRICE_PLAYER_DIALOGS_H +#define COCKATRICE_PLAYER_DIALOGS_H +#include "../../game/player/player_actions.h" +#include "player_graphics_item.h" + +#include +#include + +class PlayerGraphicsItem; +class PlayerDialogs : public QObject +{ + + Q_OBJECT + +public: + explicit PlayerDialogs(PlayerGraphicsItem *player, PlayerActions *playerActions); + +signals: + void requestDialogSemaphore(bool active); + +public slots: + void onViewTopCardsDialogRequested(int defaultNumberTopCards, int deckSize); + void onViewBottomCardsDialogRequested(int defaultNumberBottomCards, int deckSize); + void onShuffleTopDialogRequested(int defaultNumberTopCards, int maxCards); + void onShuffleBottomDialogRequested(int defaultNumberBottomCards, int maxCards); + void onMulliganDialogRequested(int startSize, int handSize, int deckSize); + void onDrawCardsDialogRequested(int defaultNumberTopCards, int deckSize); + void onMoveTopCardsToDialogRequested(int defaultNumberTopCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown); + void onMoveTopCardsUntilDialogRequested(MoveTopCardsUntilOptions options); + void onMoveBottomCardsToDialogRequested(int defaultNumberBottomCards, + int maxCards, + const QString &targetZone, + const QString &zoneDisplayName, + bool faceDown); + void onDrawBottomCardsDialogRequested(int defaultNumberBottomCards, int maxCards); + void onRollDieDialogRequested(); + void onCreateRelatedFromRelationDialogRequested(const CardItem *sourceCard, const CardRelation *cardRelation); + void onCreateTokenDialogRequested(const QStringList &predefinedTokens); + void onMoveCardXCardsFromTopDialogRequested(int defaultNumberTopCardsToPlaceBelow, int deckSize); + void onSetPTDialogRequested(const QString &oldPT); + void onSetAnnotationDialogRequested(const QString &oldAnnotation); + void onSetCardCounterDialogRequested(int counterId, const QString &oldValueForDlg); + +private: + PlayerGraphicsItem *player; + PlayerActions *playerActions; + + QWidget *dialogParent() const + { + if (auto *s = player->scene()) { + if (auto *v = s->views().value(0)) { + return v->window(); + } + } + return nullptr; + } +}; + +#endif // COCKATRICE_PLAYER_DIALOGS_H diff --git a/cockatrice/src/game/player/player_graphics_item.cpp b/cockatrice/src/game_graphics/player/player_graphics_item.cpp similarity index 63% rename from cockatrice/src/game/player/player_graphics_item.cpp rename to cockatrice/src/game_graphics/player/player_graphics_item.cpp index bcc4b7f72..e0194abda 100644 --- a/cockatrice/src/game/player/player_graphics_item.cpp +++ b/cockatrice/src/game_graphics/player/player_graphics_item.cpp @@ -1,20 +1,54 @@ #include "player_graphics_item.h" +#include "../../game/player/player_actions.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../board/abstract_card_item.h" +#include "../board/counter_general.h" #include "../hand_counter.h" #include "../zones/hand_zone.h" #include "../zones/pile_zone.h" #include "../zones/stack_zone.h" #include "../zones/table_zone.h" +#include "menu/player_menu.h" +#include "player_dialogs.h" -PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) +#include + +PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player) { connect(&SettingsCache::instance(), &SettingsCache::horizontalHandChanged, this, &PlayerGraphicsItem::rearrangeZones); connect(&SettingsCache::instance(), &SettingsCache::handJustificationChanged, this, &PlayerGraphicsItem::rearrangeZones); - connect(player, &Player::rearrangeCounters, this, &PlayerGraphicsItem::rearrangeCounters); + connect(player, &PlayerLogic::rearrangeCounters, this, &PlayerGraphicsItem::rearrangeCounters); + connect(player, &PlayerLogic::activeChanged, this, &PlayerGraphicsItem::onPlayerActiveChanged); + connect(player, &PlayerLogic::concededChanged, this, [this](int, bool c) { setVisible(!c); }); + connect(player, &PlayerLogic::zoneIdChanged, this, [this](int id) { playerArea->setPlayerZoneId(id); }); + + connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded); + connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved); + + playerMenu = new PlayerMenu(this); + + connect(playerMenu, &PlayerMenu::shortcutsActivated, this, [this]() { + for (auto *ctr : counterWidgets) { + ctr->setShortcutsActive(); + } + }); + connect(playerMenu, &PlayerMenu::shortcutsDeactivated, this, [this]() { + for (auto *ctr : counterWidgets) { + ctr->setShortcutsInactive(); + } + }); + connect(playerMenu, &PlayerMenu::retranslateRequested, this, [this]() { + for (auto *ctr : counterWidgets) { + ctr->retranslateUi(); + } + }); + + playerDialogs = new PlayerDialogs(this, player->getPlayerActions()); + + connect(playerDialogs, &PlayerDialogs::requestDialogSemaphore, player, &PlayerLogic::setDialogSemaphore); playerArea = new PlayerArea(this); @@ -25,6 +59,11 @@ PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) initializeZones(); + connect(player, &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this, + &PlayerGraphicsItem::onCustomZoneAdded); + + playerMenu->setMenusForGraphicItems(); + connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect); updateBoundingRect(); @@ -35,17 +74,12 @@ PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) void PlayerGraphicsItem::retranslateUi() { - player->getPlayerMenu()->retranslateUi(); + playerMenu->retranslateUi(); QMapIterator zoneIterator(player->getZones()); while (zoneIterator.hasNext()) { emit zoneIterator.next().value()->retranslateUi(); } - - QMapIterator counterIterator(player->getCounters()); - while (counterIterator.hasNext()) { - counterIterator.next().value()->retranslateUi(); - } } void PlayerGraphicsItem::onPlayerActiveChanged(bool _active) @@ -76,18 +110,33 @@ void PlayerGraphicsItem::initializeZones() rfgZoneGraphicsItem = new PileZone(player->getRfgZone(), this); rfgZoneGraphicsItem->setPos(base + QPointF(0, 2 * h + h2 + 10)); - tableZoneGraphicsItem = new TableZone(player->getTableZone(), this); + tableZoneGraphicsItem = new TableZone(player->getTableZone(), mirrored, this); connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect); + connect(this, &PlayerGraphicsItem::mirroredChanged, tableZoneGraphicsItem, &TableZone::setMirrored); stackZoneGraphicsItem = new StackZone(player->getStackZone(), static_cast(tableZoneGraphicsItem->boundingRect().height()), this); handZoneGraphicsItem = new HandZone(player->getHandZone(), static_cast(tableZoneGraphicsItem->boundingRect().height()), this); + connect(player->getPlayerActions(), &PlayerActions::requestSortHand, handZoneGraphicsItem, &HandZone::sortHand); connect(handZoneGraphicsItem->getLogic(), &HandZoneLogic::cardCountChanged, handCounter, &HandCounter::updateNumber); connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu); + + zoneGraphicsItems.insert(player->getDeckZone()->getName(), deckZoneGraphicsItem); + zoneGraphicsItems.insert(player->getGraveZone()->getName(), graveyardZoneGraphicsItem); + zoneGraphicsItems.insert(player->getRfgZone()->getName(), rfgZoneGraphicsItem); + zoneGraphicsItems.insert(player->getSideboardZone()->getName(), sideboardGraphicsItem); + zoneGraphicsItems.insert(player->getTableZone()->getName(), tableZoneGraphicsItem); + zoneGraphicsItems.insert(player->getStackZone()->getName(), stackZoneGraphicsItem); + zoneGraphicsItems.insert(player->getHandZone()->getName(), handZoneGraphicsItem); +} + +void PlayerGraphicsItem::onCustomZoneAdded(QString customZoneName) +{ + zoneGraphicsItems.insert(customZoneName, nullptr); // Custom zone view goes here, if we ever implement it. } QRectF PlayerGraphicsItem::boundingRect() const @@ -128,24 +177,53 @@ void PlayerGraphicsItem::setMirrored(bool _mirrored) { if (mirrored != _mirrored) { mirrored = _mirrored; + emit mirroredChanged(mirrored); rearrangeZones(); } } +void PlayerGraphicsItem::onCounterAdded(CounterState *state) +{ + AbstractCounter *widget; + if (state->getName() == "life") { + widget = playerTarget->addCounter(state); + } else { + widget = new GeneralCounter(state, player, true, this); + } + counterWidgets.insert(state->getId(), widget); + + if (playerMenu->getCountersMenu() && widget->getMenu()) { + playerMenu->getCountersMenu()->addMenu(widget->getMenu()); + } + + if (playerMenu->getShortcutsActive()) { + widget->setShortcutsActive(); + } + + rearrangeCounters(); +} + +void PlayerGraphicsItem::onCounterRemoved(int counterId) +{ + auto *widget = counterWidgets.take(counterId); + if (!widget) { + return; + } + if (playerMenu->getCountersMenu() && widget->getMenu()) { + playerMenu->getCountersMenu()->removeAction(widget->getMenu()->menuAction()); + } + widget->delCounter(); + rearrangeCounters(); +} + void PlayerGraphicsItem::rearrangeCounters() { - qreal marginTop = 80; - const qreal padding = 5; - qreal ySize = boundingRect().y() + marginTop; - - // Place objects - for (const auto &counter : player->getCounters()) { - AbstractCounter *ctr = counter; - + qreal ySize = boundingRect().y() + 80; + constexpr qreal padding = 5; + for (auto *ctr : counterWidgets.values()) { if (!ctr->getShownInCounterArea()) { continue; } - QRectF br = ctr->boundingRect(); ctr->setPos((counterAreaWidth - br.width()) / 2, ySize); ySize += br.height() + padding; @@ -158,11 +236,11 @@ void PlayerGraphicsItem::rearrangeZones() if (SettingsCache::instance().getHorizontalHand()) { if (mirrored) { if (player->getHandZone()->contentsKnown()) { - player->getPlayerInfo()->setHandVisible(true); + handVisible = true; handZoneGraphicsItem->setPos(base); base += QPointF(0, handZoneGraphicsItem->boundingRect().height()); } else { - player->getPlayerInfo()->setHandVisible(false); + handVisible = false; } stackZoneGraphicsItem->setPos(base); @@ -176,16 +254,16 @@ void PlayerGraphicsItem::rearrangeZones() base += QPointF(0, tableZoneGraphicsItem->boundingRect().height()); if (player->getHandZone()->contentsKnown()) { - player->getPlayerInfo()->setHandVisible(true); + handVisible = true; handZoneGraphicsItem->setPos(base); } else { - player->getPlayerInfo()->setHandVisible(false); + handVisible = false; } } handZoneGraphicsItem->setWidth(tableZoneGraphicsItem->getWidth() + stackZoneGraphicsItem->boundingRect().width()); } else { - player->getPlayerInfo()->setHandVisible(true); + handVisible = true; handZoneGraphicsItem->setPos(base); base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0); @@ -195,7 +273,7 @@ void PlayerGraphicsItem::rearrangeZones() tableZoneGraphicsItem->setPos(base); } - handZoneGraphicsItem->setVisible(player->getPlayerInfo()->getHandVisible()); + handZoneGraphicsItem->setVisible(handVisible); handZoneGraphicsItem->updateOrientation(); tableZoneGraphicsItem->reorganizeCards(); updateBoundingRect(); @@ -207,8 +285,7 @@ void PlayerGraphicsItem::updateBoundingRect() prepareGeometryChange(); qreal width = CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width(); if (SettingsCache::instance().getHorizontalHand()) { - qreal handHeight = - player->getPlayerInfo()->getHandVisible() ? handZoneGraphicsItem->boundingRect().height() : 0; + qreal handHeight = handVisible ? handZoneGraphicsItem->boundingRect().height() : 0; bRect = QRectF(0, 0, width + tableZoneGraphicsItem->boundingRect().width(), tableZoneGraphicsItem->boundingRect().height() + handHeight); } else { diff --git a/cockatrice/src/game/player/player_graphics_item.h b/cockatrice/src/game_graphics/player/player_graphics_item.h similarity index 74% rename from cockatrice/src/game/player/player_graphics_item.h rename to cockatrice/src/game_graphics/player/player_graphics_item.h index cba664dd9..d02234ded 100644 --- a/cockatrice/src/game/player/player_graphics_item.h +++ b/cockatrice/src/game_graphics/player/player_graphics_item.h @@ -1,18 +1,21 @@ /** * @file player_graphics_item.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_PLAYER_GRAPHICS_ITEM_H #define COCKATRICE_PLAYER_GRAPHICS_ITEM_H +#include "../../game/player/player_logic.h" +#include "../board/abstract_counter.h" #include "../game_scene.h" -#include "player.h" #include class HandZone; class PileZone; +class PlayerDialogs; +class PlayerMenu; class PlayerTarget; class StackZone; class TableZone; @@ -34,7 +37,7 @@ public: static constexpr int counterAreaWidth = 55; - explicit PlayerGraphicsItem(Player *player); + explicit PlayerGraphicsItem(PlayerLogic *player); void initializeZones(); [[nodiscard]] QRectF boundingRect() const override; @@ -54,11 +57,16 @@ public: return static_cast(scene()); } - Player *getPlayer() const + PlayerLogic *getLogic() const { return player; } + [[nodiscard]] PlayerMenu *getPlayerMenu() const + { + return playerMenu; + } + PlayerArea *getPlayerArea() const { return playerArea; @@ -69,6 +77,11 @@ public: return playerTarget; } + CardZone *getZoneGraphicsItem(const QString &name) const + { + return zoneGraphicsItems.value(name, nullptr); + } + [[nodiscard]] PileZone *getDeckZoneGraphicsItem() const { return deckZoneGraphicsItem; @@ -102,16 +115,26 @@ public: public slots: void onPlayerActiveChanged(bool _active); + void onCustomZoneAdded(QString customZoneName); + void onCounterAdded(CounterState *state); + void onCounterRemoved(int counterId); + void rearrangeCounters(); void retranslateUi(); signals: void sizeChanged(); void playerCountChanged(); + void mirroredChanged(bool isMirrored); + void cardInfoRequested(const CardRef &cardRef); private: - Player *player; + PlayerLogic *player; + PlayerMenu *playerMenu; + PlayerDialogs *playerDialogs; PlayerArea *playerArea; PlayerTarget *playerTarget; + QMap counterWidgets; + QMap zoneGraphicsItems; PileZone *deckZoneGraphicsItem; PileZone *sideboardGraphicsItem; PileZone *graveyardZoneGraphicsItem; @@ -121,11 +144,11 @@ private: HandZone *handZoneGraphicsItem; QRectF bRect; bool mirrored; + bool handVisible = false; private slots: void updateBoundingRect(); void rearrangeZones(); - void rearrangeCounters(); }; #endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game_graphics/player/player_list_widget.cpp similarity index 92% rename from cockatrice/src/game/player/player_list_widget.cpp rename to cockatrice/src/game_graphics/player/player_list_widget.cpp index 9506e0729..6b1cf6cc6 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game_graphics/player/player_list_widget.cpp @@ -43,8 +43,9 @@ PlayerListTWI::PlayerListTWI() : QTreeWidgetItem(Type) bool PlayerListTWI::operator<(const QTreeWidgetItem &other) const { // Sort by spectator/player - if (data(1, Qt::UserRole) != other.data(1, Qt::UserRole)) + if (data(1, Qt::UserRole) != other.data(1, Qt::UserRole)) { return data(1, Qt::UserRole).toBool(); + } // Sort by player ID return data(4, Qt::UserRole + 1).toInt() < other.data(4, Qt::UserRole + 1).toInt(); @@ -106,12 +107,14 @@ void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player) void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId) { - if (playerId == -1) + if (playerId == -1) { playerId = prop.player_id(); + } QTreeWidgetItem *player = players.value(playerId, 0); - if (!player) + if (!player) { return; + } bool isSpectator = prop.has_spectator() && prop.spectator(); if (prop.has_judge() || prop.has_spectator()) { @@ -126,13 +129,16 @@ void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties } if (!isSpectator) { - if (prop.has_conceded()) + if (prop.has_conceded()) { player->setData(2, Qt::UserRole, prop.conceded()); - if (prop.has_ready_start()) + } + if (prop.has_ready_start()) { player->setData(2, Qt::UserRole + 1, prop.ready_start()); - if (prop.has_conceded() || prop.has_ready_start()) + } + if (prop.has_conceded() || prop.has_ready_start()) { player->setIcon(2, gameStarted ? (prop.conceded() ? concededIcon : QIcon()) : (prop.ready_start() ? readyIcon : notReadyIcon)); + } } if (prop.has_user_info()) { player->setData(3, Qt::UserRole, prop.user_info().user_level()); @@ -141,30 +147,35 @@ void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties QString::fromStdString(prop.user_info().privlevel()))); player->setText(4, QString::fromStdString(prop.user_info().name())); const QString country = QString::fromStdString(prop.user_info().country()); - if (!country.isEmpty()) + if (!country.isEmpty()) { player->setIcon(4, QIcon(CountryPixmapGenerator::generatePixmap(12, country))); + } player->setData(4, Qt::UserRole, QString::fromStdString(prop.user_info().name())); } - if (prop.has_player_id()) + if (prop.has_player_id()) { player->setData(4, Qt::UserRole + 1, prop.player_id()); + } if (!isSpectator) { if (prop.has_deck_hash()) { player->setText(5, QString::fromStdString(prop.deck_hash())); } - if (prop.has_sideboard_locked()) + if (prop.has_sideboard_locked()) { player->setIcon(5, prop.sideboard_locked() ? lockIcon : QIcon()); + } } - if (prop.has_ping_seconds()) + if (prop.has_ping_seconds()) { player->setIcon(0, QIcon(PingPixmapGenerator::generatePixmap(12, prop.ping_seconds(), 10))); + } } void PlayerListWidget::removePlayer(int playerId) { QTreeWidgetItem *player = players.value(playerId, 0); - if (!player) + if (!player) { return; + } players.remove(playerId); delete takeTopLevelItem(indexOfTopLevelItem(player)); } @@ -193,13 +204,14 @@ void PlayerListWidget::setGameStarted(bool _gameStarted, bool resuming) QTreeWidgetItem *twi = i.next().value(); bool isPlayer = twi->data(1, Qt::UserRole).toBool(); - if (!isPlayer) + if (!isPlayer) { continue; + } if (gameStarted) { - if (resuming) + if (resuming) { twi->setIcon(2, twi->data(2, Qt::UserRole).toBool() ? concededIcon : QIcon()); - else { + } else { twi->setData(2, Qt::UserRole, false); twi->setIcon(2, QIcon()); } @@ -211,8 +223,9 @@ void PlayerListWidget::setGameStarted(bool _gameStarted, bool resuming) void PlayerListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index) { - if (!userContextMenu) + if (!userContextMenu) { return; + } const QString &userName = index.sibling(index.row(), 4).data(Qt::UserRole).toString(); int playerId = index.sibling(index.row(), 4).data(Qt::UserRole + 1).toInt(); diff --git a/cockatrice/src/game/player/player_list_widget.h b/cockatrice/src/game_graphics/player/player_list_widget.h similarity index 96% rename from cockatrice/src/game/player/player_list_widget.h rename to cockatrice/src/game_graphics/player/player_list_widget.h index 8f1487563..a53cfa989 100644 --- a/cockatrice/src/game/player/player_list_widget.h +++ b/cockatrice/src/game_graphics/player/player_list_widget.h @@ -1,13 +1,13 @@ /** * @file player_list_widget.h * @ingroup GameWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PLAYERLISTWIDGET_H #define PLAYERLISTWIDGET_H -#include "player.h" +#include "../../game/player/player_logic.h" #include #include diff --git a/cockatrice/src/game/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp similarity index 92% rename from cockatrice/src/game/player/player_target.cpp rename to cockatrice/src/game_graphics/player/player_target.cpp index a7a5cc5e7..567f3d44d 100644 --- a/cockatrice/src/game/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -1,7 +1,7 @@ #include "player_target.h" +#include "../../game/player/player_logic.h" #include "../../interface/pixel_map_generator.h" -#include "player.h" #include #include @@ -9,8 +9,8 @@ #include #include -PlayerCounter::PlayerCounter(Player *_player, int _id, const QString &_name, int _value, QGraphicsItem *parent) - : AbstractCounter(_player, _id, _name, false, _value, false, parent) +PlayerCounter::PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent) + : AbstractCounter(state, player, false, false, parent) { } @@ -47,7 +47,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value)); } -PlayerTarget::PlayerTarget(Player *_owner, QGraphicsItem *parentItem) +PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem) : ArrowTarget(_owner, parentItem), playerCounter(nullptr) { setCacheMode(DeviceCoordinateCache); @@ -128,8 +128,9 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o resetPainterTransform(painter); QString name = QString::fromStdString(info->name()); - if (name.size() > 13) + if (name.size() > 13) { name = name.mid(0, 10) + "..."; + } QFont font; font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9)); @@ -144,22 +145,21 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o painter->setPen(pen); painter->drawRect(boundingRect().adjusted(border / 2, border / 2, -border / 2, -border / 2)); - if (getBeingPointedAt()) + if (getBeingPointedAt()) { painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); + } } -AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, int _value) +AbstractCounter *PlayerTarget::addCounter(CounterState *state) { if (playerCounter) { disconnect(playerCounter, nullptr, this, nullptr); playerCounter->delCounter(); } - - playerCounter = new PlayerCounter(owner, _counterId, _name, _value, this); + playerCounter = new PlayerCounter(state, owner, this); playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(), boundingRect().height() - playerCounter->boundingRect().height()); connect(playerCounter, &PlayerCounter::destroyed, this, &PlayerTarget::counterDeleted); - return playerCounter; } diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game_graphics/player/player_target.h similarity index 69% rename from cockatrice/src/game/player/player_target.h rename to cockatrice/src/game_graphics/player/player_target.h index b60464e12..67e155660 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game_graphics/player/player_target.h @@ -1,25 +1,25 @@ /** * @file player_target.h * @ingroup GameGraphicsPlayers - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PLAYERTARGET_H #define PLAYERTARGET_H -#include "../../game_graphics/board/graphics_item_type.h" #include "../board/abstract_counter.h" #include "../board/arrow_target.h" +#include "../board/graphics_item_type.h" #include -class Player; +class PlayerLogic; class PlayerCounter : public AbstractCounter { Q_OBJECT public: - PlayerCounter(Player *_player, int _id, const QString &_name, int _value, QGraphicsItem *parent = nullptr); + PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; @@ -43,12 +43,12 @@ public: return Type; } - explicit PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr); + explicit PlayerTarget(PlayerLogic *_player = nullptr, QGraphicsItem *parentItem = nullptr); ~PlayerTarget() override; QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; - AbstractCounter *addCounter(int _counterId, const QString &_name, int _value); + AbstractCounter *addCounter(CounterState *state); }; #endif diff --git a/cockatrice/src/game/z_value_layer_manager.h b/cockatrice/src/game_graphics/z_value_layer_manager.h similarity index 94% rename from cockatrice/src/game/z_value_layer_manager.h rename to cockatrice/src/game_graphics/z_value_layer_manager.h index 4eb864486..d35ab5c1c 100644 --- a/cockatrice/src/game/z_value_layer_manager.h +++ b/cockatrice/src/game_graphics/z_value_layer_manager.h @@ -66,12 +66,9 @@ namespace ZValueLayerManager */ enum class Layer { - /// Zone-level elements like backgrounds and containers - Zone, - /// Cards rendered in zones (uses sequential Z-values) - Card, - /// Temporary UI elements like hovered cards and drag items - Overlay + Zone, ///< Zone-level elements like backgrounds and containers. + Card, ///< Cards rendered in zones (uses sequential Z-values). + Overlay ///< Temporary UI elements like hovered cards and drag items. }; /** diff --git a/cockatrice/src/game/z_values.h b/cockatrice/src/game_graphics/z_values.h similarity index 100% rename from cockatrice/src/game/z_values.h rename to cockatrice/src/game_graphics/z_values.h diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game_graphics/zones/card_zone.cpp similarity index 86% rename from cockatrice/src/game/zones/card_zone.cpp rename to cockatrice/src/game_graphics/zones/card_zone.cpp index 0c189cd2b..3457b681e 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game_graphics/zones/card_zone.cpp @@ -19,19 +19,22 @@ CardZone::CardZone(CardZoneLogic *_logic, QGraphicsItem *parent) void CardZone::onCardAdded(CardItem *addedCard) { addedCard->setParentItem(this); + addedCard->setVisible(true); addedCard->update(); } void CardZone::retranslateUi() { - for (int i = 0; i < getLogic()->getCards().size(); ++i) + for (int i = 0; i < getLogic()->getCards().size(); ++i) { getLogic()->getCards()[i]->retranslateUi(); + } } void CardZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/) { - if (doubleClickAction) + if (doubleClickAction) { doubleClickAction->trigger(); + } } bool CardZone::showContextMenu(const QPoint &screenPos) @@ -46,12 +49,14 @@ bool CardZone::showContextMenu(const QPoint &screenPos) void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::RightButton) { - if (showContextMenu(event->screenPos())) + if (showContextMenu(event->screenPos())) { event->accept(); - else + } else { event->ignore(); - } else + } + } else { event->ignore(); + } } QPointF CardZone::closestGridPoint(const QPointF &point) diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game_graphics/zones/card_zone.h similarity index 73% rename from cockatrice/src/game/zones/card_zone.h rename to cockatrice/src/game_graphics/zones/card_zone.h index 6fe8157e4..4cef6ca80 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game_graphics/zones/card_zone.h @@ -1,15 +1,15 @@ /** * @file card_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. + * @brief Base graphics item for zones that contain cards. */ #ifndef CARDZONE_H #define CARDZONE_H -#include "../../game_graphics/board/abstract_graphics_item.h" -#include "../../game_graphics/board/graphics_item_type.h" -#include "logic/card_zone_logic.h" +#include "../../game/zones/card_zone_logic.h" +#include "../board/abstract_graphics_item.h" +#include "../board/graphics_item_type.h" #include #include @@ -40,7 +40,13 @@ protected: } public slots: bool showContextMenu(const QPoint &screenPos); - void onCardAdded(CardItem *addedCard); + /** + * @brief Called when a card is added to this zone. Default: reparents card to this item. + * + * Virtual so subclasses (e.g. SelectZone) can override parenting behavior — the Qt signal + * connection in CardZone's constructor dispatches through the vtable. + */ + virtual void onCardAdded(CardItem *addedCard); public: enum diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game_graphics/zones/hand_zone.cpp similarity index 60% rename from cockatrice/src/game/zones/hand_zone.cpp rename to cockatrice/src/game_graphics/zones/hand_zone.cpp index 7badfcca4..5885e3630 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game_graphics/zones/hand_zone.cpp @@ -1,11 +1,11 @@ #include "hand_zone.h" #include "../../client/settings/cache_settings.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" #include "../../interface/theme_manager.h" #include "../board/card_drag_item.h" #include "../board/card_item.h" -#include "../player/player.h" -#include "../player/player_actions.h" #include #include @@ -27,16 +27,20 @@ void HandZone::handleDropEvent(const QList &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) { + if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) { + return; + } + QPoint point = dropPoint + scenePos().toPoint(); int x = -1; if (SettingsCache::instance().getHorizontalHand()) { - for (x = 0; x < getLogic()->getCards().size(); x++) - if (point.x() < static_cast(getLogic()->getCards().at(x))->scenePos().x()) + for (x = 0; x < getLogic()->getCards().size(); x++) { + if (point.x() < static_cast(getLogic()->getCards().at(x))->scenePos().x()) { break; + } + } } else { - for (x = 0; x < getLogic()->getCards().size(); x++) - if (point.y() < static_cast(getLogic()->getCards().at(x))->scenePos().y()) - break; + x = calcDropIndexFromY(dropPoint.y()); } Command_MoveCard cmd; @@ -47,18 +51,20 @@ void HandZone::handleDropEvent(const QList &dragItems, cmd.set_x(x); cmd.set_y(-1); - for (int i = 0; i < dragItems.size(); ++i) + for (int i = 0; i < dragItems.size(); ++i) { cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + } getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd); } QRectF HandZone::boundingRect() const { - if (SettingsCache::instance().getHorizontalHand()) + if (SettingsCache::instance().getHorizontalHand()) { return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10); - else - return QRectF(0, 0, 100, zoneHeight); + } else { + return QRectF(0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight); + } } void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -78,35 +84,31 @@ void HandZone::reorganizeCards() qreal totalWidth = leftJustified ? boundingRect().width() - (1 * xPadding) - 5 : boundingRect().width() - 2 * xPadding; - for (int i = 0; i < cardCount; i++) { - CardItem *c = getLogic()->getCards().at(i); - // If the total width of the cards is smaller than the available width, - // the cards do not need to overlap and are displayed in the center of the area. - if (cardWidth * cardCount > totalWidth) - c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5); - else { - qreal xPosition = - leftJustified ? xPadding + ((qreal)i) * cardWidth - : xPadding + ((qreal)i) * cardWidth + (totalWidth - cardCount * cardWidth) / 2; - c->setPos(xPosition, 5); + if (cardCount == 1) { + CardItem *c = getLogic()->getCards().at(0); + qreal xPosition = leftJustified ? xPadding : xPadding + (totalWidth - cardWidth) / 2; + c->setPos(xPosition, 5); + c->setRealZValue(0); + } else { + for (int i = 0; i < cardCount; i++) { + CardItem *c = getLogic()->getCards().at(i); + // If the total width of the cards is smaller than the available width, + // the cards do not need to overlap and are displayed in the center of the area. + if (cardWidth * cardCount > totalWidth) { + c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5); + } else { + qreal xPosition = leftJustified ? xPadding + ((qreal)i) * cardWidth + : xPadding + ((qreal)i) * cardWidth + + (totalWidth - cardCount * cardWidth) / 2; + c->setPos(xPosition, 5); + } + c->setRealZValue(i); } - c->setRealZValue(i); } } else { - qreal totalWidth = boundingRect().width(); - qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width(); - qreal xspace = 5; - qreal x1 = xspace; - qreal x2 = totalWidth - xspace - cardWidth; - - for (int i = 0; i < cardCount; i++) { - CardItem *card = getLogic()->getCards().at(i); - qreal x = (i % 2) ? x2 : x1; - qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(), - getLogic()->getCards().at(0)->boundingRect().height()); - card->setPos(x, y); - card->setRealZValue(i); - } + // No clip container: hand cards should always be visible to the player. + const auto params = buildStackParams(); + layoutCardsVertically(params); } } update(); diff --git a/cockatrice/src/game/zones/hand_zone.h b/cockatrice/src/game_graphics/zones/hand_zone.h similarity index 80% rename from cockatrice/src/game/zones/hand_zone.h rename to cockatrice/src/game_graphics/zones/hand_zone.h index 25f4148bd..c3f3f252b 100644 --- a/cockatrice/src/game/zones/hand_zone.h +++ b/cockatrice/src/game_graphics/zones/hand_zone.h @@ -1,20 +1,21 @@ /** * @file hand_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. + * @brief Graphical zone for the player's hand, supporting horizontal and vertical layouts. */ #ifndef HANDZONE_H #define HANDZONE_H -#include "logic/hand_zone_logic.h" +#include "../../game/zones/hand_zone_logic.h" #include "select_zone.h" class HandZone : public SelectZone { Q_OBJECT private: - qreal width, zoneHeight; + qreal width = 0.0; + qreal zoneHeight; private slots: void updateBg(); public slots: diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game_graphics/zones/pile_zone.cpp similarity index 91% rename from cockatrice/src/game/zones/pile_zone.cpp rename to cockatrice/src/game_graphics/zones/pile_zone.cpp index d85b5f4e2..7bb0e695a 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game_graphics/zones/pile_zone.cpp @@ -1,10 +1,10 @@ #include "pile_zone.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/pile_zone_logic.h" #include "../board/card_drag_item.h" #include "../board/card_item.h" -#include "../player/player.h" -#include "../player/player_actions.h" -#include "logic/pile_zone_logic.h" #include "view_zone.h" #include @@ -48,9 +48,10 @@ void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*optio { painter->drawPath(shape()); - if (!getLogic()->getCards().isEmpty()) + if (!getLogic()->getCards().isEmpty()) { getLogic()->getCards().at(0)->paintPicture(painter, getLogic()->getCards().at(0)->getTranslatedSize(painter), 90); + } painter->translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F); painter->rotate(-90); @@ -87,24 +88,28 @@ void PileZone::reorganizeCards() void PileZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { CardZone::mousePressEvent(event); - if (event->isAccepted()) + if (event->isAccepted()) { return; + } if (event->button() == Qt::LeftButton) { setCursor(Qt::ClosedHandCursor); event->accept(); - } else + } else { event->ignore(); + } } void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < - QApplication::startDragDistance()) + QApplication::startDragDistance()) { return; + } - if (getLogic()->getCards().isEmpty()) + if (getLogic()->getCards().isEmpty()) { return; + } bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier); @@ -123,7 +128,8 @@ void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/) void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { - if (!getLogic()->getCards().isEmpty()) + if (!getLogic()->getCards().isEmpty()) { getLogic()->getCards()[0]->processHoverEvent(); + } QGraphicsItem::hoverEnterEvent(event); } diff --git a/cockatrice/src/game/zones/pile_zone.h b/cockatrice/src/game_graphics/zones/pile_zone.h similarity index 93% rename from cockatrice/src/game/zones/pile_zone.h rename to cockatrice/src/game_graphics/zones/pile_zone.h index 5bfc50880..099bc6f60 100644 --- a/cockatrice/src/game/zones/pile_zone.h +++ b/cockatrice/src/game_graphics/zones/pile_zone.h @@ -1,14 +1,14 @@ /** * @file pile_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PILEZONE_H #define PILEZONE_H +#include "../../game/zones/pile_zone_logic.h" #include "card_zone.h" -#include "logic/pile_zone_logic.h" /** * A CardZone where the cards are in a single pile instead of being laid out. diff --git a/cockatrice/src/game_graphics/zones/select_zone.cpp b/cockatrice/src/game_graphics/zones/select_zone.cpp new file mode 100644 index 000000000..f2e720686 --- /dev/null +++ b/cockatrice/src/game_graphics/zones/select_zone.cpp @@ -0,0 +1,288 @@ +#include "select_zone.h" + +#include "../../client/settings/cache_settings.h" +#include "../board/card_item.h" +#include "../game_scene.h" + +#include +#include +#include + +static qreal stackingOffset(qreal cardHeight) +{ + const qreal overlapPercent = SettingsCache::instance().getStackCardOverlapPercent(); + return cardHeight * (100.0 - overlapPercent) / 100.0; +} + +SelectZone::ZoneLayout SelectZone::computeZoneLayout(const StackLayoutParams ¶ms) +{ + if (params.cardCount <= 0) { + return {0.0, 0.0}; + } + qreal effectiveOffset = params.desiredOffset; + if (params.cardCount > 1) { + qreal fitOffset; + if (params.totalHeight < params.cardHeight && params.minOffset > 0.0) { + // Zone is shorter than a card (e.g. minimized). Compress offsets so + // every card has at least minOffset pixels of its top visible. + fitOffset = (params.totalHeight - params.minOffset) / (params.cardCount - 1); + effectiveOffset = qMax(0.0, qMin(params.desiredOffset, fitOffset)); + } else { + qreal reservedForBottomCard; + if (params.allowBottomOverflow) { + // Allow the bottom card to partially overflow in tight zones, scaling the + // overflow allowance by sqrt(cardCount-1) so offsets decrease smoothly + // as cards are added rather than dropping by 1/(n-1) each time. + // The 0.75 ratio was tuned experimentally to balance card visibility vs. overflow. + constexpr qreal bottomCardZoneRatio = 0.75; + const qreal adjustedRatio = bottomCardZoneRatio / qSqrt(static_cast(params.cardCount - 1)); + reservedForBottomCard = qMin(params.cardHeight, params.totalHeight * adjustedRatio); + } else { + // No overflow: reserve full card height for the bottom card + reservedForBottomCard = params.cardHeight; + } + fitOffset = (params.totalHeight - reservedForBottomCard) / (params.cardCount - 1); + + if (!params.allowBottomOverflow) { + // Constrain offset so all card tops remain within zone bounds. + // With start=0, last card top at (cardCount-1) * effectiveOffset must be < totalHeight. + qreal maxOffsetForTops = params.totalHeight / (params.cardCount - 1); + fitOffset = qMin(fitOffset, maxOffsetForTops); + } + + // Apply minOffset only if it fits; otherwise compress further to keep all card tops visible. + effectiveOffset = qMin(params.desiredOffset, fitOffset); + if (fitOffset >= params.minOffset) { + effectiveOffset = qMax(params.minOffset, effectiveOffset); + } + } + } + qreal stackHeight = (params.cardCount - 1) * effectiveOffset + params.cardHeight; + qreal start = (stackHeight <= params.totalHeight) ? (params.totalHeight - stackHeight) / 2.0 : 0.0; + return {effectiveOffset, start}; +} + +SelectZone *SelectZone::findOwningSelectZone(const QGraphicsItem *card) +{ + QGraphicsItem *parent = card ? card->parentItem() : nullptr; + if (!parent) { + return nullptr; + } + // Card may be direct child of zone (escaped for hover) or child of clip container. + if (auto *zone = dynamic_cast(parent)) { + return zone; + } + if (auto *zone = dynamic_cast(parent->parentItem())) { + return zone; + } + return nullptr; +} + +SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) const +{ + const auto &cards = getLogic()->getCards(); + if (cards.isEmpty()) { + return {0, boundingRect().height(), 0.0, 0.0, minOffset}; + } + const auto cardCount = static_cast(cards.size()); + const qreal cardHeight = cards.at(0)->boundingRect().height(); + const qreal offset = stackingOffset(cardHeight); + return {cardCount, boundingRect().height(), cardHeight, offset, minOffset}; +} + +int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const +{ + const auto &cards = getLogic()->getCards(); + if (cards.isEmpty()) { + return 0; + } + const auto params = buildStackParams(minOffset); + auto [effectiveOffset, start] = computeZoneLayout(params); + if (effectiveOffset <= 0.0) { + return 0; + } + return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1); +} + +void SelectZone::restoreStaleEscapedCards() +{ + if (!cardClipContainer) { + return; + } + for (auto *card : getLogic()->getCards()) { + // A card parented to the zone (instead of the clip container) should + // only occur while it is actively hovered. If hover cleanup was + // missed, reparent it back so clipping resumes. + if (card && card->parentItem() == this && !card->getIsHovered()) { + card->setParentItem(cardClipContainer); + } + } +} + +void SelectZone::layoutCardsVertically(const StackLayoutParams ¶ms) +{ + const auto &cards = getLogic()->getCards(); + if (cards.isEmpty() || params.cardCount <= 0) { + return; + } + if (params.cardCount > cards.size()) { + return; + } + + constexpr qreal xspace = 5; + const qreal cardWidth = cards.at(0)->boundingRect().width(); + const qreal totalWidth = boundingRect().width(); + const qreal x1 = xspace; + const qreal x2 = totalWidth - xspace - cardWidth; + const qreal xCentered = (totalWidth - cardWidth) / 2.0; + + auto [effectiveOffset, start] = computeZoneLayout(params); + for (int i = 0; i < params.cardCount; i++) { + CardItem *card = cards.at(i); + qreal y = start + i * effectiveOffset; + // Center single card; alternate left/right for multiple cards + qreal x = (params.cardCount == 1) ? xCentered : ((i % 2) ? x2 : x1); + card->setPos(x, y); + card->setRealZValue(i); + } +} + +SelectZone::SelectZone(CardZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_logic, parent) +{ +} + +SelectZone::~SelectZone() +{ + if (cardClipContainer) { + // Reparent any hover-escaped cards back to the clip container so Qt's + // parent-child tree is consistent for destruction. setParentItem() does + // not invalidate getLogic()->getCards() (it modifies the graphics tree, + // not the zone's logical card list). + for (auto *card : getLogic()->getCards()) { + if (card && card->parentItem() == this) { + card->setParentItem(cardClipContainer); + } + } + } +} + +void SelectZone::onCardAdded(CardItem *addedCard) +{ + if (cardClipContainer && addedCard) { + addedCard->setParentItem(cardClipContainer); + addedCard->setVisible(true); + addedCard->update(); + } else { + CardZone::onCardAdded(addedCard); + } +} + +void SelectZone::setupClipContainer(std::optional zValue) +{ + if (cardClipContainer) { + return; + } + + setFlag(QGraphicsItem::ItemClipsChildrenToShape, false); + + cardClipContainer = new QGraphicsRectItem(this); // Owned by Qt parent-child tree; deleted with this zone. + cardClipContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); + cardClipContainer->setPen(Qt::NoPen); + cardClipContainer->setBrush(Qt::NoBrush); + cardClipContainer->setRect(boundingRect()); + if (zValue.has_value()) { + cardClipContainer->setZValue(*zValue); + } +} + +void SelectZone::escapeClipForHover(QGraphicsItem *card) +{ + // Reparent from clip container to zone so the hover-scaled card is visible + // beyond clip bounds. Coordinates are identical because the clip container + // is at (0,0) with no transform relative to this zone. + if (cardClipContainer && card && card->parentItem() == cardClipContainer) { + card->setParentItem(this); + cardClipContainer->update(); + } +} + +void SelectZone::restoreClipAfterHover(QGraphicsItem *card) +{ + // Restore card to clip container. If card's parent is not this zone, + // a zone transition already reparented it via onCardAdded — skip. + if (cardClipContainer && card && card->parentItem() == this) { + card->setParentItem(cardClipContainer); + } +} + +void SelectZone::updateClipRect() +{ + if (cardClipContainer) { + cardClipContainer->setRect(boundingRect()); + } +} + +void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->buttons().testFlag(Qt::LeftButton)) { + QPointF pos = event->pos(); + if (pos.x() < 0) { + pos.setX(0); + } + QRectF br = boundingRect(); + if (pos.x() > br.width()) { + pos.setX(br.width()); + } + if (pos.y() < 0) { + pos.setY(0); + } + if (pos.y() > br.height()) { + pos.setY(br.height()); + } + + QRectF selectionRect = QRectF(selectionOrigin, pos).normalized(); + for (auto card : getLogic()->getCards()) { + if (card->getAttachedTo() && card->getAttachedTo()->getZone() != getLogic()) { + continue; + } + + bool inRect = selectionRect.intersects(card->mapRectToItem(this, card->boundingRect())); + if (inRect && !cardsInSelectionRect.contains(card)) { + // selection has just expanded to cover the card + cardsInSelectionRect.insert(card); + card->setSelected(!card->isSelected()); + } else if (!inRect && cardsInSelectionRect.contains(card)) { + // selection has just shrunk to no longer cover the card + cardsInSelectionRect.remove(card); + card->setSelected(!card->isSelected()); + } + } + static_cast(scene())->resizeRubberBand( + deviceTransform(static_cast(scene())->getViewportTransform()).map(pos), + cardsInSelectionRect.size()); + event->accept(); + } +} + +void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (!event->modifiers().testFlag(Qt::ControlModifier)) { + scene()->clearSelection(); + } + + selectionOrigin = event->pos(); + static_cast(scene())->startRubberBand(event->scenePos()); + event->accept(); + } else { + CardZone::mousePressEvent(event); + } +} + +void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + selectionOrigin = QPoint(); + cardsInSelectionRect.clear(); + static_cast(scene())->stopRubberBand(); + event->accept(); +} diff --git a/cockatrice/src/game_graphics/zones/select_zone.h b/cockatrice/src/game_graphics/zones/select_zone.h new file mode 100644 index 000000000..7408f29b6 --- /dev/null +++ b/cockatrice/src/game_graphics/zones/select_zone.h @@ -0,0 +1,148 @@ +/** + * @file select_zone.h + * @ingroup GameGraphicsZones + * @brief Base class for zones where cards are laid out and individually interactable. + */ + +#ifndef SELECTZONE_H +#define SELECTZONE_H + +#include "card_zone.h" + +#include +#include + +class QGraphicsRectItem; + +/** + * A CardZone where the cards are laid out, with each card directly interactable by clicking. + */ +class SelectZone : public CardZone +{ + Q_OBJECT +public: + /** + * @brief Finds the SelectZone that owns a card, regardless of whether the card is parented + * to the zone directly or to its clip container. Returns nullptr if not in a SelectZone. + */ + static SelectZone *findOwningSelectZone(const QGraphicsItem *card); + + SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr); + ~SelectZone() override; + void onCardAdded(CardItem *addedCard) override; + + /** + * @brief Temporarily reparents a card from the clip container to this zone so hover scaling is visible beyond clip + * bounds. Safe no-op if no clip container exists. Coordinates are preserved (clip container is at (0,0) with no + * transform). + */ + void escapeClipForHover(QGraphicsItem *card); + /** + * @brief Restores a hover-escaped card back to the clip container. Guards against zone transitions that already + * reparented the card. + */ + void restoreClipAfterHover(QGraphicsItem *card); + +private: + QPointF selectionOrigin; + QSet cardsInSelectionRect; + /** + * @brief Invisible clipping parent for cards; owned by Qt parent-child tree (parented to this zone). + * Created by setupClipContainer(); null when no clip container is active. + */ + QGraphicsRectItem *cardClipContainer = nullptr; + +protected: + // -- Layout computation -- + + /** @brief Parameters describing a vertical card stack's geometry. */ + struct StackLayoutParams + { + int cardCount; ///< Number of cards in the stack + qreal totalHeight; ///< Available height for the stack (zone height) + qreal cardHeight; ///< Height of a single card + qreal desiredOffset; ///< Preferred vertical offset between card tops + qreal minOffset = 0.0; ///< Minimum offset to preserve (0 allows full compression) + /** + * @brief When false (default), reserves full cardHeight for the bottom card, ensuring + * all cards remain within zone bounds. When true, allows the bottom card to + * partially overflow using sqrt-scaled allowance. Use with setupClipContainer() + * for zones too short to fit a full card. + */ + bool allowBottomOverflow = false; + }; + + /** @brief Result of computing a vertical stack layout. */ + struct ZoneLayout + { + qreal effectiveOffset; ///< Actual offset between card tops (may be compressed) + qreal start; ///< Y coordinate of the first card's top edge + }; + + /** @brief Minimum visible pixels of each card's top edge when stacking compresses offsets in tight zones. */ + static constexpr qreal MIN_CARD_VISIBLE = 10.0; + + /** + * @brief Computes layout for a vertical card stack (effective offset and start position). + * + * Three regimes: + * 1. Minimized zone (totalHeight < card height with minOffset > 0): offsets compress + * so each card retains at least minOffset visible pixels of its top edge. + * 2. Normal zone with allowBottomOverflow=false (default): the bottom card is + * guaranteed to fit within the zone boundary. Offsets compress as needed. + * 3. Normal zone with allowBottomOverflow=true: the bottom card may partially + * overflow. The overflow allowance is scaled by sqrt(cardCount-1) so that + * adding one card shifts existing cards smoothly. + * + * When the stack fits with room to spare, it is centered vertically. + */ + static ZoneLayout computeZoneLayout(const StackLayoutParams ¶ms); + + /** @brief Builds StackLayoutParams from the current card list and zone geometry. */ + StackLayoutParams buildStackParams(qreal minOffset = 0.0) const; + + /** + * @brief Computes the card index at a given y-coordinate within the zone's vertical layout. + * Returns 0 if the zone has no cards or the offset is zero. + */ + int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const; + + /** + * @brief Positions cards vertically with alternating left/right x-offsets. + * + * Cards alternate between left and right margins (5px padding from zone edges): + * even-indexed cards at left, odd-indexed at right. + * Cards are assigned ascending z-values. + * + * @param params Stack layout geometry parameters (use allowBottomOverflow to control overflow) + */ + void layoutCardsVertically(const StackLayoutParams ¶ms); + + // -- Clip container -- + // The clip container mechanism is available for future zones that need visual clipping + // (e.g., zones too short to fit a full card). To enable: call setupClipContainer() in the + // zone's constructor, and set allowBottomOverflow=true in layout params. + + /** + * @brief Restores any cards that were hover-escaped but whose hover state was not properly cleaned up. + * Call at the start of reorganizeCards() in zones that use a clip container. + */ + void restoreStaleEscapedCards(); + + /** + * @brief Creates a clip container child item that clips card overflow to zone bounds. + * Cards entering this zone are reparented to this container by the onCardAdded override. + * Disables zone-level child clipping; clipping is delegated to the container. + * @param zValue Optional z-value for the clip container (e.g. ZValues::CARD_BASE) + */ + void setupClipContainer(std::optional zValue = std::nullopt); + + /** @brief Updates the clip container rect to match this zone's current boundingRect(). */ + void updateClipRect(); + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; +}; + +#endif diff --git a/cockatrice/src/game_graphics/zones/stack_zone.cpp b/cockatrice/src/game_graphics/zones/stack_zone.cpp new file mode 100644 index 000000000..46ff099ab --- /dev/null +++ b/cockatrice/src/game_graphics/zones/stack_zone.cpp @@ -0,0 +1,93 @@ +#include "stack_zone.h" + +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/stack_zone_logic.h" +#include "../../interface/theme_manager.h" +#include "../board/card_drag_item.h" +#include "../board/card_item.h" +#include "../card_dimensions.h" + +#include +#include + +StackZone::StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent) + : SelectZone(_logic, parent), zoneHeight(_zoneHeight) +{ + connect(themeManager, &ThemeManager::themeChanged, this, &StackZone::updateBg); + updateBg(); + setCacheMode(DeviceCoordinateCache); +} + +void StackZone::updateBg() +{ + update(); +} + +QRectF StackZone::boundingRect() const +{ + return {0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight}; +} + +void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) +{ + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId()); + painter->fillRect(boundingRect(), brush); +} + +void StackZone::handleDropEvent(const QList &dragItems, + CardZoneLogic *startZone, + const QPoint &dropPoint) +{ + if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) { + return; + } + + int index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE); + + // Same-zone no-op: don't move a card onto itself + const auto &cards = getLogic()->getCards(); + if (!cards.isEmpty() && startZone == getLogic() && cards.at(index)->getId() == dragItems.at(0)->getId()) { + return; + } + + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId()); + cmd.set_target_zone(getLogic()->getName().toStdString()); + cmd.set_x(index); + cmd.set_y(0); + + for (const CardDragItem *item : dragItems) { + if (item) { + auto *cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(item->getId()); + if (item->isForceFaceDown()) { + cardToMove->set_face_down(true); + } + } + } + + getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd); +} + +void StackZone::setHeight(qreal newHeight) +{ + if (qFuzzyCompare(1.0 + zoneHeight, 1.0 + newHeight)) { + return; + } + prepareGeometryChange(); + zoneHeight = newHeight; + reorganizeCards(); + update(); +} + +void StackZone::reorganizeCards() +{ + if (!getLogic()->getCards().isEmpty()) { + const auto params = buildStackParams(MIN_CARD_VISIBLE); + layoutCardsVertically(params); + } + update(); +} diff --git a/cockatrice/src/game/zones/stack_zone.h b/cockatrice/src/game_graphics/zones/stack_zone.h similarity index 71% rename from cockatrice/src/game/zones/stack_zone.h rename to cockatrice/src/game_graphics/zones/stack_zone.h index 7c98f5128..147c3e2fc 100644 --- a/cockatrice/src/game/zones/stack_zone.h +++ b/cockatrice/src/game_graphics/zones/stack_zone.h @@ -1,13 +1,13 @@ /** * @file stack_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. + * @brief Graphical zone for the stack, displaying cards in a vertical pile. */ #ifndef STACKZONE_H #define STACKZONE_H -#include "logic/stack_zone_logic.h" +#include "../../game/zones/stack_zone_logic.h" #include "select_zone.h" class StackZone : public SelectZone @@ -20,6 +20,8 @@ private slots: public: StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent); + /** @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone. */ + void setHeight(qreal newHeight); void handleDropEvent(const QList &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override; QRectF boundingRect() const override; diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game_graphics/zones/table_zone.cpp similarity index 90% rename from cockatrice/src/game/zones/table_zone.cpp rename to cockatrice/src/game_graphics/zones/table_zone.cpp index 2a382fafe..e886f62e9 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game_graphics/zones/table_zone.cpp @@ -1,14 +1,14 @@ #include "table_zone.h" #include "../../client/settings/cache_settings.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/table_zone_logic.h" #include "../../interface/theme_manager.h" #include "../board/arrow_item.h" #include "../board/card_drag_item.h" #include "../board/card_item.h" -#include "../player/player.h" -#include "../player/player_actions.h" #include "../z_values.h" -#include "logic/table_zone_logic.h" #include #include @@ -22,7 +22,8 @@ const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80); const QColor TableZone::GRADIENT_COLOR = QColor(255, 255, 255, 150); const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0); -TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone(_logic, parent), active(false) +TableZone::TableZone(TableZoneLogic *_logic, bool _mirrored, QGraphicsItem *parent) + : SelectZone(_logic, parent), active(false), mirrored(_mirrored) { connect(_logic, &TableZoneLogic::contentSizeChanged, this, &TableZone::resizeToContents); connect(_logic, &TableZoneLogic::toggleTapped, this, &TableZone::toggleTapped); @@ -50,12 +51,16 @@ QRectF TableZone::boundingRect() const return QRectF(0, 0, width, height); } +void TableZone::setMirrored(bool isMirrored) +{ + mirrored = isMirrored; + update(); +} + bool TableZone::isInverted() const { - return ((getLogic()->getPlayer()->getGraphicsItem()->getMirrored() && - !SettingsCache::instance().getInvertVerticalCoordinate()) || - (!getLogic()->getPlayer()->getGraphicsItem()->getMirrored() && - SettingsCache::instance().getInvertVerticalCoordinate())); + return ((mirrored && !SettingsCache::instance().getInvertVerticalCoordinate()) || + (!mirrored && SettingsCache::instance().getInvertVerticalCoordinate())); } void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -108,8 +113,9 @@ void TableZone::paintLandDivider(QPainter *painter) // Place the line 2 grid heights down then back it off just enough to allow // some space between a 3-card stack and the land area. qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2; - if (isInverted()) + if (isInverted()) { separatorY = height - separatorY; + } painter->setPen(QColor(255, 255, 255, 40)); painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); } @@ -157,8 +163,9 @@ void TableZone::reorganizeCards() for (int i = 0; i < getLogic()->getCards().size(); ++i) { QPoint gridPoint = getLogic()->getCards()[i]->getGridPos(); - if (gridPoint.x() == -1) + if (gridPoint.x() == -1) { continue; + } QPointF mapPoint = mapFromGrid(gridPoint); qreal x = mapPoint.x(); @@ -167,8 +174,9 @@ void TableZone::reorganizeCards() int numberAttachedCards = getLogic()->getCards()[i]->getAttachedCards().size(); qreal actualX = x + numberAttachedCards * STACKED_CARD_OFFSET_X; qreal actualY = y; - if (numberAttachedCards) + if (numberAttachedCards) { actualY += 15; + } getLogic()->getCards()[i]->setPos(actualX, actualY); getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY)); @@ -227,16 +235,19 @@ void TableZone::resizeToContents() int xMax = 0; // Find rightmost card position, which includes the left margin amount. - for (int i = 0; i < getLogic()->getCards().size(); ++i) - if (getLogic()->getCards()[i]->pos().x() > xMax) + for (int i = 0; i < getLogic()->getCards().size(); ++i) { + if (getLogic()->getCards()[i]->pos().x() > xMax) { xMax = (int)getLogic()->getCards()[i]->pos().x(); + } + } // Minimum width is the rightmost card position plus enough room for // another card with padding, then margin. currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT; - if (currentMinimumWidth < MIN_WIDTH) + if (currentMinimumWidth < MIN_WIDTH) { currentMinimumWidth = MIN_WIDTH; + } if (currentMinimumWidth != width) { prepareGeometryChange(); @@ -247,9 +258,11 @@ void TableZone::resizeToContents() CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const { - for (int i = 0; i < getLogic()->getCards().size(); i++) - if (getLogic()->getCards().at(i)->getGridPoint() == gridPoint) + for (int i = 0; i < getLogic()->getCards().size(); i++) { + if (getLogic()->getCards().at(i)->getGridPoint() == gridPoint) { return getLogic()->getCards().at(i); + } + } return 0; } @@ -266,8 +279,9 @@ void TableZone::computeCardStackWidths() QMap cardStackCount; for (int i = 0; i < getLogic()->getCards().size(); ++i) { const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos(); - if (gridPoint.x() == -1) + if (gridPoint.x() == -1) { continue; + } const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y()); cardStackCount.insert(key, cardStackCount.value(key, 0) + 1); @@ -277,16 +291,18 @@ void TableZone::computeCardStackWidths() cardStackWidth.clear(); for (int i = 0; i < getLogic()->getCards().size(); ++i) { const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos(); - if (gridPoint.x() == -1) + if (gridPoint.x() == -1) { continue; + } const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y()); const int stackCount = cardStackCount.value(key, 0); - if (stackCount == 1) + if (stackCount == 1) { cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() * STACKED_CARD_OFFSET_X); - else + } else { cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X); + } } } @@ -303,15 +319,17 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X; } - if (isInverted()) + if (isInverted()) { gridPoint.setY(TABLEROWS - 1 - gridPoint.y()); + } // Start with margin plus stacked card offset y = MARGIN_TOP + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_Y; // Add in card size and padding for each row - for (int i = 0; i < gridPoint.y(); ++i) + for (int i = 0; i < gridPoint.y(); ++i) { y += CardDimensions::HEIGHT + PADDING_Y; + } return QPointF(x, y); } @@ -330,8 +348,9 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const gridPointY = clampValidTableRow(gridPointY); - if (isInverted()) + if (isInverted()) { gridPointY = TABLEROWS - 1 - gridPointY; + } // Calculating the x-coordinate of the grid space requires adding up the // widths of each card stack along the row. @@ -367,19 +386,23 @@ QPointF TableZone::closestGridPoint(const QPointF &point) { QPoint gridPoint = mapToGrid(point); gridPoint.setX((gridPoint.x() / 3) * 3); - if (getCardFromGrid(gridPoint)) + if (getCardFromGrid(gridPoint)) { gridPoint.setX(gridPoint.x() + 1); - if (getCardFromGrid(gridPoint)) + } + if (getCardFromGrid(gridPoint)) { gridPoint.setX(gridPoint.x() + 1); + } return mapFromGrid(gridPoint); } int TableZone::clampValidTableRow(const int row) { - if (row < 0) + if (row < 0) { return 0; - if (row >= TABLEROWS) + } + if (row >= TABLEROWS) { return TABLEROWS - 1; + } return row; } diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game_graphics/zones/table_zone.h similarity index 91% rename from cockatrice/src/game/zones/table_zone.h rename to cockatrice/src/game_graphics/zones/table_zone.h index 7a53a9eb4..0d7e58206 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game_graphics/zones/table_zone.h @@ -1,23 +1,22 @@ /** * @file table_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TABLEZONE_H #define TABLEZONE_H +#include "../../game/zones/table_zone_logic.h" #include "../board/abstract_card_item.h" -#include "logic/table_zone_logic.h" #include "select_zone.h" -/* - * TableZone is the grid based rect where CardItems may be placed. - * It is the main play zone and can be customized with background images. +/** + * @brief TableZone is the grid based rect where CardItems may be placed. * - * TODO: Refactor methods to make more readable, extract some logic to - * private methods (Im looking at you TableZone::reorganizeCards()) + * It is the main play zone and can be customized with background images. */ +//! \todo Refactor methods to make more readable, extract logic to private methods (especially reorganizeCards()). class TableZone : public SelectZone { Q_OBJECT @@ -83,6 +82,7 @@ private: If this TableZone is currently active */ bool active = false; + bool mirrored = false; [[nodiscard]] bool isInverted() const; @@ -97,6 +97,7 @@ public slots: Reorganizes CardItems in the TableZone */ void reorganizeCards() override; + void setMirrored(bool isMirrored); public: /** @@ -105,7 +106,7 @@ public: @param _p the Player @param parent defaults to null */ - explicit TableZone(TableZoneLogic *_logic, QGraphicsItem *parent = nullptr); + explicit TableZone(TableZoneLogic *_logic, bool mirrored, QGraphicsItem *parent = nullptr); /** @return a QRectF of the TableZone bounding box. diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game_graphics/zones/view_zone.cpp similarity index 97% rename from cockatrice/src/game/zones/view_zone.cpp rename to cockatrice/src/game_graphics/zones/view_zone.cpp index d2fd1e971..baf7b8b30 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game_graphics/zones/view_zone.cpp @@ -1,10 +1,10 @@ #include "view_zone.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" +#include "../../game/zones/view_zone_logic.h" #include "../board/card_drag_item.h" #include "../board/card_item.h" -#include "../player/player.h" -#include "../player/player_actions.h" -#include "logic/view_zone_logic.h" #include #include @@ -203,9 +203,9 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca QString columnProp = extractor(c); if (i) { // if not the first card - if (columnProp == lastColumnProp) + if (columnProp == lastColumnProp) { row++; // add below current card - else { // if no match then move card to next column + } else { // if no match then move card to next column col++; row = 0; } @@ -233,8 +233,9 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca cols = qCeil((double)cardCount / minRows); } - if (cols < 2) + if (cols < 2) { cols = 2; + } qCDebug(ViewZoneLog) << "reorganizeCards: rows=" << rows << "cols=" << cols; diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game_graphics/zones/view_zone.h similarity index 97% rename from cockatrice/src/game/zones/view_zone.h rename to cockatrice/src/game_graphics/zones/view_zone.h index 81279c72e..9dfa00ce2 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game_graphics/zones/view_zone.h @@ -1,13 +1,13 @@ /** * @file view_zone.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ZONEVIEWERZONE_H #define ZONEVIEWERZONE_H -#include "logic/view_zone_logic.h" +#include "../../game/zones/view_zone_logic.h" #include "select_zone.h" #include diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp similarity index 98% rename from cockatrice/src/game/zones/view_zone_widget.cpp rename to cockatrice/src/game_graphics/zones/view_zone_widget.cpp index 23d7d6a19..4a5d064d0 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp @@ -2,11 +2,11 @@ #include "../../client/settings/cache_settings.h" #include "../../filters/syntax_help.h" +#include "../../game/player/player_actions.h" +#include "../../game/player/player_logic.h" #include "../../interface/pixel_map_generator.h" #include "../board/card_item.h" #include "../game_scene.h" -#include "../player/player.h" -#include "../player/player_actions.h" #include "../z_values.h" #include "view_zone.h" @@ -37,7 +37,7 @@ constexpr qreal kMinVisibleWidth = 100.0; * @param _revealZone if false, the cards will be face down. * @param _writeableRevealZone whether the player can interact with the revealed cards. */ -ZoneViewWidget::ZoneViewWidget(Player *_player, +ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player, CardZoneLogic *_origZone, int numberCards, bool _revealZone, @@ -252,8 +252,9 @@ void ZoneViewWidget::retranslateUi() void ZoneViewWidget::stopWindowDrag() { - if (!draggingWindow) + if (!draggingWindow) { return; + } draggingWindow = false; ungrabMouse(); @@ -312,13 +313,15 @@ QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const { QWidget *current = eventWidget; while (current) { - if (auto *view = qobject_cast(current)) + if (auto *view = qobject_cast(current)) { return view; + } current = current->parentWidget(); } - if (scene() && !scene()->views().isEmpty()) + if (scene() && !scene()->views().isEmpty()) { return scene()->views().constFirst(); + } return nullptr; } @@ -346,8 +349,9 @@ bool ZoneViewWidget::windowFrameEvent(QEvent *event) } auto *me = dynamic_cast(event); - if (!me) + if (!me) { return QGraphicsWidget::windowFrameEvent(event); + } switch (event->type()) { case QEvent::GraphicsSceneMousePress: @@ -506,8 +510,9 @@ void ZoneViewWidget::resizeToZoneContents(bool forceInitialHeight) zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight)); - if (layout()) + if (layout()) { layout()->invalidate(); + } } void ZoneViewWidget::handleScrollBarChange(int value) @@ -521,8 +526,9 @@ void ZoneViewWidget::closeEvent(QCloseEvent *event) disconnect(zone, &ZoneViewZone::closed, this, 0); // manually call zone->close in order to remove it from the origZones views zone->close(); - if (shuffleCheckBox.isChecked()) + if (shuffleCheckBox.isChecked()) { player->getPlayerActions()->sendGameCommand(Command_Shuffle()); + } zoneDeleted(); event->accept(); } @@ -536,8 +542,9 @@ void ZoneViewWidget::zoneDeleted() void ZoneViewWidget::initStyleOption(QStyleOption *option) const { QStyleOptionTitleBar *titleBar = qstyleoption_cast(option); - if (titleBar) + if (titleBar) { titleBar->icon = QPixmap("theme:cockatrice"); + } } /** diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game_graphics/zones/view_zone_widget.h similarity index 95% rename from cockatrice/src/game/zones/view_zone_widget.h rename to cockatrice/src/game_graphics/zones/view_zone_widget.h index 1246192b8..de5ad28d5 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game_graphics/zones/view_zone_widget.h @@ -1,12 +1,12 @@ /** * @file view_zone_widget.h * @ingroup GameGraphicsZones - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ZONEVIEWWIDGET_H #define ZONEVIEWWIDGET_H -#include "logic/card_zone_logic.h" +#include "../../game/zones/card_zone_logic.h" #include #include @@ -20,7 +20,7 @@ class QLabel; class QPushButton; class CardZone; class ZoneViewZone; -class Player; +class PlayerLogic; class CardDatabase; class QScrollBar; class GameScene; @@ -65,7 +65,7 @@ private: bool canBeShuffled; int extraHeight; - Player *player; + PlayerLogic *player; bool draggingWindow = false; QPoint dragStartScreenPos; @@ -108,7 +108,7 @@ private slots: void expandWindow(); public: - ZoneViewWidget(Player *_player, + ZoneViewWidget(PlayerLogic *_player, CardZoneLogic *_origZone, int numberCards = 0, bool _revealZone = false, @@ -119,7 +119,7 @@ public: { return zone; } - Player *getPlayer() const + PlayerLogic *getPlayer() const { return player; } diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp index dbd51b973..bd427a6c8 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -150,9 +150,10 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image) { + QPixmap finalPixmap; + if (image.isNull()) { qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName(); - QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap()); } else { if (card.getInfo().getUiAttributes().upsideDownArt) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) @@ -160,12 +161,19 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image) #else QImage mirrorImage = image.mirrored(true, true); #endif - QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(mirrorImage)); + finalPixmap = QPixmap::fromImage(mirrorImage); } else { - QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(image)); + finalPixmap = QPixmap::fromImage(image); } } + QPixmapCache::insert(card.getPixmapCacheKey(), finalPixmap); + + if (SettingsCache::instance().getCardPictureLoaderCacheMethod() == + CardPictureLoaderCacheMethod::CacheMethod::FILESYSTEM_CACHE) { + saveCardImageToLocalStorage(card, finalPixmap); + } + // imageLoaded should only be reached if the exactCard isn't already in cache. // (plus there's a deduplication mechanism in CardPictureLoaderWorker) // It should be safe to connect the CardInfo here without worrying about redundant connections. @@ -175,6 +183,88 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image) card.emitPixmapUpdated(); } +void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap) +{ + if (pixmap.isNull() || !card) { + return; + } + + const QString picsRoot = SettingsCache::instance().getPicsPath(); + CardPictureLoaderLocalSchemes::NamingScheme scheme = + SettingsCache::instance().getLocalCardImageStorageNamingScheme(); + + QString pattern; + + for (const auto &s : CardPictureLoaderLocalSchemes::exportSchemes()) { + if (s.id == scheme) { + pattern = s.pattern; + break; + } + } + + if (picsRoot.isEmpty() || pattern.isEmpty()) { + return; + } + + // Base directory: /downloadedPics + QDir baseDir(picsRoot); + if (!baseDir.exists("downloadedPics")) { + baseDir.mkpath("downloadedPics"); + } + baseDir.cd("downloadedPics"); + + // Collect card metadata + const QString cardName = card.getInfo().getCorrectedName(); + + QString setName; + QString collectorNumber; + QString uuid; + + PrintingInfo printing = card.getPrinting(); + if (printing.getSet()) { + setName = printing.getSet()->getCorrectedShortName(); + collectorNumber = printing.getProperty("num"); + uuid = printing.getUuid(); + } + + // Build path from scheme + QString relativePath = + CardPictureLoaderLocalSchemes::expandPattern(pattern, cardName, setName, collectorNumber, uuid); + + if (relativePath.isEmpty()) { + return; + } + + // append extension + relativePath += ".png"; + + // Normalize slashes + relativePath = QDir::cleanPath(relativePath); + + QFileInfo outInfo(baseDir.filePath(relativePath)); + + // Do not overwrite existing files + if (outInfo.exists()) { + return; + } + + QDir outDir = outInfo.dir(); + + // Ensure directory exists + if (!outDir.exists()) { + if (!baseDir.mkpath(outDir.path())) { + qCWarning(CardPictureLoaderLog) << "Failed to create directory for downloaded card image:" << outDir.path(); + return; + } + } + + // Save image + QImage image = pixmap.toImage(); + if (!image.save(outInfo.absoluteFilePath(), "PNG")) { + qCWarning(CardPictureLoaderLog) << "Failed to save card image to" << outInfo.absoluteFilePath(); + } +} + void CardPictureLoader::clearPixmapCache() { QPixmapCache::clear(); @@ -230,8 +320,9 @@ bool CardPictureLoader::hasCustomArt() QFileInfo dir(it.next()); #endif - if (it.fileName() == "downloadedPics") + if (it.fileName() == "downloadedPics") { continue; + } QDirIterator subIt(it.filePath(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); if (subIt.hasNext()) { diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h index 4000fd99a..0c114ae92 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h @@ -117,6 +117,7 @@ public slots: * @param image Loaded QImage. */ void imageLoaded(const ExactCard &card, const QImage &image); + void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap); private slots: /** diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h new file mode 100644 index 000000000..54977e249 --- /dev/null +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h @@ -0,0 +1,31 @@ +#ifndef COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H +#define COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H +#include +#include +#include + +namespace CardPictureLoaderCacheMethod +{ +enum class CacheMethod +{ + NETWORK_CACHE, + FILESYSTEM_CACHE +}; + +struct CacheMethodInfo +{ + CacheMethod id; + QString displayName; +}; + +static inline const QList methods() +{ + static QList all = { + {CacheMethod::NETWORK_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Network Cache")}, + {CacheMethod::FILESYSTEM_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Filesystem")}, + }; + return all; +} +} // namespace CardPictureLoaderCacheMethod + +#endif // COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp index dd00e6e1d..bb10d1c42 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp @@ -1,6 +1,7 @@ #include "card_picture_loader_local.h" #include "../../client/settings/cache_settings.h" +#include "card_picture_loader_local_schemes.h" #include "card_picture_to_load.h" #include @@ -77,26 +78,8 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName, imgReader.setDecideFormatFromContent(true); // Most-to-least specific, these will fall through in order. - QStringList nameVariants; - - // cardName_providerId - if (!providerId.isEmpty()) { - nameVariants << QString("%1-%2").arg(correctedCardName, providerId) - << QString("%1_%2").arg(correctedCardName, providerId); - } - // cardName_setName_collectorNumber & setName-collectorNumber-cardName - if (!setName.isEmpty() && !collectorNumber.isEmpty()) { - nameVariants << QString("%1_%2_%3").arg(correctedCardName, setName, collectorNumber) - << QString("%1-%2-%3").arg(setName, collectorNumber, correctedCardName); - } - // cardName_setName - if (!setName.isEmpty()) { - nameVariants << QString("%1_%2").arg(correctedCardName, setName) - << QString("%1-%2").arg(setName, correctedCardName); - } - - // cardName - nameVariants << correctedCardName; + QStringList nameVariants = + CardPictureLoaderLocalSchemes::generateImportVariants(correctedCardName, setName, collectorNumber, providerId); for (const QString &nameVariant : nameVariants) { if (nameVariant.isEmpty()) { diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h new file mode 100644 index 000000000..d51d646e6 --- /dev/null +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h @@ -0,0 +1,116 @@ +#ifndef COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H +#define COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H + +#include +#include +#include +#include + +namespace CardPictureLoaderLocalSchemes +{ + +enum class NamingScheme +{ + NameOnly, + Name_Set, + Name_Set_Collector, + Set_Collector_Name, + Name_ProviderId, + Set_Folder_Name_ProviderId, + Set_Folder_Name_Set_Collector +}; + +struct NamingSchemeInfo +{ + NamingScheme id; + QString displayName; + QString pattern; +}; + +inline const QList &importSchemes() +{ + static QList list = { + {NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"}, + {NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"}, + {NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"}, + {NamingScheme::Name_Set, "Card Name + Set", "{name}_{set}"}, + {NamingScheme::NameOnly, "Card Name", "{name}"}, + }; + return list; +} + +inline const QList &exportSchemes() +{ + static QList list = { + {NamingScheme::Set_Folder_Name_ProviderId, "Set Folder / Name + Provider ID", "{set}/{name}_{providerId}"}, + {NamingScheme::Set_Folder_Name_Set_Collector, "Set Folder / Name + Set Name + Collector", + "{set}/{name}_{set}_{collector}"}, + {NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"}, + {NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"}, + {NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"}, + }; + return list; +} + +inline QString expandPattern(const QString &pattern, + const QString &name, + const QString &set, + const QString &collector, + const QString &providerId) +{ + QString result = pattern; + + auto replaceIfPresent = [&](const QString &token, const QString &value) -> bool { + if (!result.contains(token)) { + return true; + } + + if (value.isEmpty()) { + return false; + } + + result.replace(token, value); + return true; + }; + + if (!replaceIfPresent("{name}", name)) { + return {}; + } + if (!replaceIfPresent("{set}", set)) { + return {}; + } + if (!replaceIfPresent("{collector}", collector)) { + return {}; + } + if (!replaceIfPresent("{providerId}", providerId)) { + return {}; + } + + return result; +} + +inline QStringList +generateImportVariants(const QString &name, const QString &set, const QString &collector, const QString &providerId) +{ + QStringList variants; + const QStringList separators = {"_", "-"}; + + for (const auto &scheme : importSchemes()) { + for (const QString &sep : separators) { + + QString pattern = scheme.pattern; + pattern.replace("_", sep); + + QString v = expandPattern(pattern, name, set, collector, providerId); + if (!v.isEmpty()) { + variants << v; + } + } + } + + return variants; +} + +} // namespace CardPictureLoaderLocalSchemes + +#endif // COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H \ No newline at end of file diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp index a246d74f2..2f51ba986 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp @@ -26,10 +26,15 @@ CardPictureLoaderWorker::CardPictureLoaderWorker() 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, this, - [this](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); + + connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, [this](int newSizeInMB) { + if (cache) { + 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); @@ -65,14 +70,19 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor QUrl cachedRedirect = getCachedRedirect(url); if (!cachedRedirect.isEmpty()) { queueRequest(cachedRedirect, worker); - } else if (cache->metaData(url).isValid()) { - // If we hit a cached url, we get to make the request for free, since it won't contribute towards the rate-limit - makeRequest(url, worker); - } else { - requestLoadQueue.append(qMakePair(url, worker)); - emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName()); - processQueuedRequests(); + return; } + if (SettingsCache::instance().getCardPictureLoaderCacheMethod() == + CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE && + cache->metaData(url).isValid()) { + // If we hit a cached url, we get to make the request for free, since it won't contribute towards the + // rate-limit + makeRequest(url, worker); + return; + } + requestLoadQueue.append(qMakePair(url, worker)); + emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName()); + processQueuedRequests(); } QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker) @@ -87,9 +97,12 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8"); - if (!picDownload) { - req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); - } + + bool useNetworkCache = !picDownload && SettingsCache::instance().getCardPictureLoaderCacheMethod() == + CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE; + + req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork); QNetworkReply *reply = networkManager->get(req); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp index d72226aab..63846e5fd 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp @@ -53,18 +53,19 @@ QList CardPictureToLoad::extractSetsSorted(const ExactCard &card) * PrintingInfo for the set is returned. * * This method only exists to maintain existing behavior. - * TODO: check if going through all sets is still necessary after the ExactCard refactor. * * @param card The card to look in * @param setName The set's short name * @return A PrintingInfo, or a default-constructed PrintingInfo if the set name is not in the CardInfo. */ +//! \todo Check if going through all sets is still necessary after the ExactCard refactor. static PrintingInfo findPrintingForSet(const ExactCard &card, const QString &setName) { SetToPrintingsMap setsToPrintings = card.getInfo().getSets(); - if (!setsToPrintings.contains(setName)) + if (!setsToPrintings.contains(setName)) { return PrintingInfo(); + } for (const auto &printing : setsToPrintings[setName]) { if (printing.getUuid() == card.getPrinting().getUuid()) { diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index d71dca24a..39a0c1071 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,10 @@ std::optional DeckLoader::loadFromRemote(const QString &nativeString std::optional DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt) { - QFile file(fileName); + // Use QSaveFile so that a failed write (e.g. a full disk) leaves the existing deck untouched + // instead of truncating it to a 0-byte file. The target is only replaced once every byte has + // been flushed successfully in commit(). + QSaveFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(DeckLoaderLog) << "Could not create or open file:" << fileName; return std::nullopt; @@ -145,15 +149,19 @@ DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFo break; } - file.flush(); - file.close(); - - qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt << "-" << success; - if (!success) { + file.cancelWriting(); + qCWarning(DeckLoaderLog) << "Failed to serialize deck for file:" << fileName; return std::nullopt; } + if (!file.commit()) { + qCWarning(DeckLoaderLog) << "Failed to save deck to " << fileName << ":" << file.errorString(); + return std::nullopt; + } + + qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt; + LoadedDeck::LoadInfo lastLoadInfo = {fileName, fmt}; return lastLoadInfo; } @@ -181,6 +189,11 @@ bool DeckLoader::saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFi */ bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck) { + // text format doesn't support lastLoadedTimestamp, so there's no point in proceeding + if (deck.lastLoadInfo.fileFormat != DeckFileFormat::Cockatrice) { + return false; + } + QString fileName = deck.lastLoadInfo.fileName; QFileInfo fileInfo(fileName); @@ -191,45 +204,44 @@ bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck) QDateTime originalTimestamp = fileInfo.lastModified(); - // Open the file for writing - QFile file(fileName); + // Use QSaveFile so that a failed write (e.g. a full disk) cannot truncate an existing deck to a + // 0-byte file while merely bumping its timestamp. + QSaveFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << fileName; return false; } - bool result = false; - // Perform file modifications - switch (deck.lastLoadInfo.fileFormat) { - case DeckFileFormat::PlainText: - result = deck.deckList.saveToFile_Plain(&file); - break; - case DeckFileFormat::Cockatrice: - deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = deck.deckList.saveToFile_Native(&file); - break; + deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); + + if (!deck.deckList.saveToFile_Native(&file)) { + file.cancelWriting(); + qCWarning(DeckLoaderLog) << "Failed to serialize deck for file:" << fileName; + return false; } - file.close(); // Close the file to ensure changes are flushed - - if (result) { - // Re-open the file and set the original timestamp - if (!file.open(QIODevice::ReadWrite)) { - qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; - return false; - } - - if (!file.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) { - qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName; - file.close(); - return false; - } - - file.close(); + if (!file.commit()) { + qCWarning(DeckLoaderLog) << "Failed to update timestamp for file:" << fileName << ":" << file.errorString(); + return false; } - return result; + // Re-open the file and restore the original timestamp, so that updating the lastLoadedTimestamp + // does not change the file's modification time. + QFile timestampFile(fileName); + if (!timestampFile.open(QIODevice::ReadWrite)) { + qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; + return false; + } + + if (!timestampFile.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) { + qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName; + timestampFile.close(); + return false; + } + + timestampFile.close(); + return true; } static QString getDomainForWebsite(DeckLoader::DecklistWebsite website) @@ -429,8 +441,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, } if (addSetNameAndNumber) { if (!card->getCardSetShortName().isNull() && !card->getCardSetShortName().isEmpty()) { - out << " " - << "(" << card->getCardSetShortName() << ")"; + out << " " << "(" << card->getCardSetShortName() << ")"; } if (!card->getCardCollectorNumber().isNull()) { out << " " << card->getCardCollectorNumber(); @@ -447,51 +458,54 @@ bool DeckLoader::convertToCockatriceFormat(LoadedDeck &deck) return false; } + // Determine the format before touching any file, so an already-converted or + // unsupported deck never truncates or deletes anything. + switch (DeckFileFormat::getFormatFromName(fileName)) { + case DeckFileFormat::PlainText: + break; + case DeckFileFormat::Cockatrice: + qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; + return true; + default: + qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName; + return false; + } + // Change the file extension to .cod QFileInfo fileInfo(fileName); QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); - // Open the new file for writing - QFile file(newFileName); + // Use QSaveFile so a failed write (e.g. a full disk) cannot leave a 0-byte .cod + // behind and then delete the original deck. + QSaveFile file(newFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << newFileName; return false; } - bool result = false; - - // Perform file modifications based on the detected format - switch (DeckFileFormat::getFormatFromName(fileName)) { - case DeckFileFormat::PlainText: - // Save in Cockatrice's native format - result = deck.deckList.saveToFile_Native(&file); - break; - case DeckFileFormat::Cockatrice: - qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; - result = true; - break; - default: - qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName; - result = false; - break; + if (!deck.deckList.saveToFile_Native(&file)) { + file.cancelWriting(); + qCWarning(DeckLoaderLog) << "Failed to serialize deck for file:" << newFileName; + return false; } - file.close(); - - // Delete the old file if conversion was successful - if (result) { - if (!QFile::remove(fileName)) { - qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName; - } else { - qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; - } - deck.lastLoadInfo = { - .fileName = newFileName, - .fileFormat = DeckFileFormat::Cockatrice, - }; + if (!file.commit()) { + qCWarning(DeckLoaderLog) << "Failed to convert deck to " << newFileName << ":" << file.errorString(); + return false; } - return result; + // Conversion succeeded: delete the original file. + if (!QFile::remove(fileName)) { + qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName; + } else { + qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; + } + deck.lastLoadInfo = { + .fileName = newFileName, + .fileFormat = DeckFileFormat::Cockatrice, + }; + + return true; } void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 2530b7f5d..ac23e1ee0 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -1,8 +1,8 @@ /** * @file deck_loader.h * @ingroup ImportExport - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_LOADER_H #define DECK_LOADER_H @@ -13,6 +13,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader"); diff --git a/cockatrice/src/interface/key_signals.cpp b/cockatrice/src/interface/key_signals.cpp index b4a38fad5..a78997cc2 100644 --- a/cockatrice/src/interface/key_signals.cpp +++ b/cockatrice/src/interface/key_signals.cpp @@ -6,29 +6,33 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event) { QKeyEvent *kevent; - if (event->type() != QEvent::KeyPress) + if (event->type() != QEvent::KeyPress) { return false; + } kevent = static_cast(event); switch (kevent->key()) { case Qt::Key_Return: case Qt::Key_Enter: - if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) + if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { emit onCtrlAltEnter(); - else if (kevent->modifiers() & Qt::ControlModifier) + } else if (kevent->modifiers() & Qt::ControlModifier) { emit onCtrlEnter(); - else + } else { emit onEnter(); + } break; case Qt::Key_Right: - if (kevent->modifiers() & Qt::ShiftModifier) + if (kevent->modifiers() & Qt::ShiftModifier) { emit onShiftRight(); + } break; case Qt::Key_Left: - if (kevent->modifiers() & Qt::ShiftModifier) + if (kevent->modifiers() & Qt::ShiftModifier) { emit onShiftLeft(); + } break; case Qt::Key_Delete: @@ -37,33 +41,39 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event) break; case Qt::Key_Minus: - if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) + if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { emit onCtrlAltMinus(); + } break; case Qt::Key_Equal: - if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) + if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { emit onCtrlAltEqual(); + } break; case Qt::Key_BracketLeft: - if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) + if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { emit onCtrlAltLBracket(); + } break; case Qt::Key_BracketRight: - if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) + if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { emit onCtrlAltRBracket(); + } break; case Qt::Key_S: - if (kevent->modifiers() & Qt::ShiftModifier) + if (kevent->modifiers() & Qt::ShiftModifier) { emit onShiftS(); + } break; case Qt::Key_C: - if (kevent->modifiers() & Qt::ControlModifier) + if (kevent->modifiers() & Qt::ControlModifier) { emit onCtrlC(); + } break; default: diff --git a/cockatrice/src/interface/key_signals.h b/cockatrice/src/interface/key_signals.h index 3404dca9e..30dcee0ba 100644 --- a/cockatrice/src/interface/key_signals.h +++ b/cockatrice/src/interface/key_signals.h @@ -2,8 +2,8 @@ * @file key_signals.h * @ingroup Core * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef KEYSIGNALS_H #define KEYSIGNALS_H diff --git a/cockatrice/src/interface/layouts/flow_layout.cpp b/cockatrice/src/interface/layouts/flow_layout.cpp index f7ebbfb79..0d03b7789 100644 --- a/cockatrice/src/interface/layouts/flow_layout.cpp +++ b/cockatrice/src/interface/layouts/flow_layout.cpp @@ -1,7 +1,16 @@ /** * @file flow_layout.cpp - * @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets - * in rows within the constraints of available width or parent scroll areas. + * @brief Implementation of FlowLayout — a QLayout that wraps child widgets into rows + * (Qt::Horizontal flow) or columns (Qt::Vertical flow). + * + * Design contract (following Qt layout conventions): + * - setGeometry() places children inside the given rect. Nothing else. + * - sizeHint() reports the unconstrained preferred size (all items in one line). + * - minimumSize() reports the minimum size (largest single item). + * - heightForWidth() reports the height needed for a given width (horizontal flow only). + * + * The layout never calls setFixedSize() or adjustSize() on its parent widget; + * that is the responsibility of the parent widget / scroll area. */ #include "flow_layout.h" @@ -12,27 +21,18 @@ #include #include #include +#include -/** - * @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values. - * @param parent The parent widget for this layout. - * @param margin The layout margin. - * @param hSpacing The horizontal spacing between items. - * @param vSpacing The vertical spacing between items. - */ FlowLayout::FlowLayout(QWidget *parent, - const Qt::Orientation _flowDirection, + const Qt::Orientation flowDirection, const int margin, const int hSpacing, const int vSpacing) - : QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing) + : QLayout(parent), flowDirection(flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing) { setContentsMargins(margin, margin, margin, margin); } -/** - * @brief Destructor for FlowLayout, which cleans up all items in the layout. - */ FlowLayout::~FlowLayout() { QLayoutItem *item; @@ -42,499 +42,353 @@ FlowLayout::~FlowLayout() } /** - * @brief Indicates the layout's support for expansion in both horizontal and vertical directions. - * @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill. + * @brief Reports which axis the layout can expand along. + * + * A horizontally-flowing layout expands horizontally (and wraps vertically, + * but that is governed by heightForWidth, not by this flag). + * A vertically-flowing layout expands vertically. */ Qt::Orientations FlowLayout::expandingDirections() const { - return Qt::Horizontal | Qt::Vertical; + return (flowDirection == Qt::Horizontal) ? Qt::Horizontal : Qt::Vertical; } /** - * @brief Indicates that this layout's height depends on its width. - * @return True, as the layout adjusts its height to fit the specified width. + * @brief Height-for-width is only meaningful for horizontal (wrapping) flow. */ bool FlowLayout::hasHeightForWidth() const { - return true; + return flowDirection == Qt::Horizontal; } /** - * @brief Calculates the required height to display all items within the specified width. - * @param width The available width for arranging items. - * @return The total height needed to fit all items in rows constrained by the specified width. + * @brief Returns the height required to display all items within @p width. + * + * Only valid for horizontal flow; returns -1 otherwise so Qt ignores it. + * Spacing is counted once between adjacent items, never before the first + * or after the last. */ int FlowLayout::heightForWidth(const int width) const { - if (flowDirection == Qt::Vertical) { - int height = 0; - int rowWidth = 0; - int rowHeight = 0; - - for (const QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - int itemWidth = item->sizeHint().width() + horizontalSpacing(); - if (rowWidth + itemWidth > width) { - height += rowHeight + verticalSpacing(); - rowWidth = itemWidth; - rowHeight = item->sizeHint().height(); - } else { - rowWidth += itemWidth; - rowHeight = qMax(rowHeight, item->sizeHint().height()); - } - } - height += rowHeight; // Add height of the last row - return height; - } else { - int width = 0; - int colWidth = 0; - int colHeight = 0; - - for (const QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - int itemHeight = item->sizeHint().height(); - if (colHeight + itemHeight > width) { - width += colWidth; - colHeight = itemHeight; - colWidth = item->sizeHint().width(); - } else { - colHeight += itemHeight; - colWidth = qMax(colWidth, item->sizeHint().width()); - } - } - width += colWidth; // Add width of the last column - return width; + if (flowDirection != Qt::Horizontal) { + return -1; } -} -/** - * @brief Arranges layout items in rows within the specified rectangle bounds. - * @param rect The area within which to position layout items. - */ -void FlowLayout::setGeometry(const QRect &rect) -{ - QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle. + int totalHeight = 0; + int rowUsedWidth = 0; + int rowHeight = 0; - if (flowDirection == Qt::Horizontal) { - // If we have a parent scroll area, we're clamped to that, else we use our own rectangle. - const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth(); - - const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth); - - if (QWidget *parentWidgetPtr = parentWidget()) { - parentWidgetPtr->setFixedSize(availableWidth, totalHeight); - } - } else { - const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight()); - - const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight); - - if (QWidget *parentWidgetPtr = parentWidget()) { - parentWidgetPtr->setFixedSize(totalWidth, availableHeight); - } - } -} - -/** - * @brief Lays out items into rows according to the available width, starting from a given origin. - * Each row is arranged within `availableWidth`, wrapping to a new row as necessary. - * @param originX The x-coordinate for the layout start position. - * @param originY The y-coordinate for the layout start position. - * @param availableWidth The width within which each row is constrained. - * @return The total height after arranging all rows. - */ -int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth) -{ - QVector rowItems; // Holds items for the current row - int currentXPosition = originX; // Tracks the x-coordinate while placing items - int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row - - int rowHeight = 0; // Tracks the maximum height of items in the current row - - for (QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { + for (const QLayoutItem *item : items) { + if (!item || item->isEmpty()) { continue; } - QSize itemSize = item->sizeHint(); // The suggested size for the current item - int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing + const QSize itemSize = item->sizeHint(); + // Spacing is only inserted between items, not before the first. + const int spaceX = (rowUsedWidth > 0) ? horizontalSpacing() : 0; - // Check if the current item fits in the remaining width of the current row - if (currentXPosition + itemWidth > availableWidth) { - // If not, layout the current row and start a new row - layoutSingleRow(rowItems, originX, currentYPosition); - rowItems.clear(); // Reset the list for the new row - currentXPosition = originX; // Reset x-position to the row's start - currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row - rowHeight = 0; // Reset row height for the new row + if (rowUsedWidth > 0 && rowUsedWidth + spaceX + itemSize.width() > width) { + // This item overflows the current row — commit the row and start a new one. + totalHeight += rowHeight + verticalSpacing(); + rowUsedWidth = itemSize.width(); + rowHeight = itemSize.height(); + } else { + rowUsedWidth += spaceX + itemSize.width(); + rowHeight = qMax(rowHeight, itemSize.height()); + } + } + + return totalHeight + rowHeight; // Include the final (possibly only) row. +} + +/** + * @brief Places all children within @p rect. + * + * This is the only method that may move/resize children. It does NOT resize + * the parent widget; that would break Qt's layout protocol. + */ +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + + if (flowDirection == Qt::Horizontal) { + layoutAllRows(rect.x(), rect.y(), rect.width()); + } else { + layoutAllColumns(rect.x(), rect.y(), rect.height()); + } +} + +QSize FlowLayout::sizeHint() const +{ + return (flowDirection == Qt::Horizontal) ? calculateSizeHintHorizontal() : calculateSizeHintVertical(); +} + +QSize FlowLayout::minimumSize() const +{ + return (flowDirection == Qt::Horizontal) ? calculateMinimumSizeHorizontal() : calculateMinimumSizeVertical(); +} + +// ─── Row layout (horizontal flow) ──────────────────────────────────────────── + +/** + * @brief Places all items into wrapping rows within @p availableWidth. + * @return The y-coordinate of the bottom edge of the last row. + */ +int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth) +{ + QVector rowItems; + int rowUsedWidth = 0; // Width consumed by items already in the current row. + int currentY = originY; + int rowHeight = 0; + + for (QLayoutItem *item : items) { + if (!item || item->isEmpty()) { + continue; + } + + const QSize itemSize = item->sizeHint(); + // No leading space for the first item in a row. + const int spaceX = rowItems.isEmpty() ? 0 : horizontalSpacing(); + + if (!rowItems.isEmpty() && rowUsedWidth + spaceX + itemSize.width() > availableWidth) { + // Current item does not fit — flush the current row, begin a new one. + layoutSingleRow(rowItems, originX, currentY, availableWidth); + rowItems.clear(); + currentY += rowHeight + verticalSpacing(); + rowUsedWidth = 0; + rowHeight = 0; } // Add the item to the current row rowItems.append(item); - rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item - currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item + // `rowItems.size() > 1` is equivalent to "this is not the first item in the row" + // because we just appended above. + rowUsedWidth += (rowItems.size() > 1 ? horizontalSpacing() : 0) + itemSize.width(); + rowHeight = qMax(rowHeight, itemSize.height()); } - // Layout the final row if there are any remaining items - layoutSingleRow(rowItems, originX, currentYPosition); - - // Return the total height used, including the last row's height - return currentYPosition + rowHeight; + layoutSingleRow(rowItems, originX, currentY, availableWidth); // Flush the final row. + return currentY + rowHeight; } /** - * @brief Arranges a single row of items within specified x and y starting positions. - * @param rowItems A list of items to be arranged in the row. - * @param x The starting x-coordinate for the row. - * @param y The starting y-coordinate for the row. + * @brief Sets the geometry for every item in @p rowItems, starting at (@p x, @p y). + * + * Items whose horizontal size policy includes the Expand or MinimumExpanding flag + * share the leftover row width proportionally (like QHBoxLayout stretch), so that + * e.g. a QLineEdit can fill remaining space while fixed-size buttons stay compact. + * + * Items without an expanding policy are placed at their sizeHint, clamped to maximumSize. */ -void FlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y) +void FlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y, const int availableWidth) { + if (rowItems.isEmpty()) { + return; + } + + // ── Pass 1: measure fixed width and count expanding items ──────────────── + int fixedWidth = 0; + int expandingCount = 0; + int spacingTotal = (rowItems.size() - 1) * horizontalSpacing(); + for (QLayoutItem *item : rowItems) { - if (item == nullptr || item->isEmpty()) { + if (!item || item->isEmpty()) { continue; } - // Get the maximum allowed size for the item - QSize itemMaxSize = item->widget()->maximumSize(); - // Constrain the item's width and height to its size hint or maximum size - const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); - const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); - // Set the item's geometry based on the computed size and position + QWidget *widget = item->widget(); + const QSizePolicy::Policy hPolicy = widget ? widget->sizePolicy().horizontalPolicy() : QSizePolicy::Fixed; + + if (hPolicy & QSizePolicy::ExpandFlag) { + ++expandingCount; + } else { + const int maxW = widget ? widget->maximumWidth() : QWIDGETSIZE_MAX; + fixedWidth += qMin(item->sizeHint().width(), maxW); + } + } + + // Extra pixels to share among expanding items (never negative). + const int extra = qMax(0, availableWidth - spacingTotal - fixedWidth); + const int expandingShare = (expandingCount > 0) ? extra / expandingCount : 0; + + // ── Pass 2: place items ────────────────────────────────────────────────── + for (QLayoutItem *item : rowItems) { + if (!item || item->isEmpty()) { + continue; + } + + QWidget *widget = item->widget(); + if (!widget) { + continue; + } + + const QSizePolicy::Policy hPolicy = widget->sizePolicy().horizontalPolicy(); + const QSize maxSize = widget->maximumSize(); + const bool expands = hPolicy & QSizePolicy::ExpandFlag; + + const int itemWidth = + expands ? qMin(expandingShare, maxSize.width()) : qMin(item->sizeHint().width(), maxSize.width()); + const int itemHeight = qMin(item->sizeHint().height(), maxSize.height()); + item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); - // Move the x-position to the right, leaving space for horizontal spacing x += itemWidth + horizontalSpacing(); } } +// ─── Column layout (vertical flow) ─────────────────────────────────────────── + /** - * @brief Lays out items into columns according to the available height, starting from a given origin. - * Each column is arranged within `availableHeight`, wrapping to a new column as necessary. - * @param originX The x-coordinate for the layout start position. - * @param originY The y-coordinate for the layout start position. - * @param availableHeight The height within which each column is constrained. - * @return The total width after arranging all columns. + * @brief Places all items into wrapping columns within @p availableHeight. + * @return The x-coordinate of the right edge of the last column. */ int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight) { - QVector colItems; // Holds items for the current column - int currentXPosition = originX; // Tracks the x-coordinate while placing items - int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column - - int colWidth = 0; // Tracks the maximum width of items in the current column + QVector colItems; + int colUsedHeight = 0; // Height consumed by items already in the current column. + int currentX = originX; + int colWidth = 0; for (QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { + if (!item || item->isEmpty()) { continue; } - QSize itemSize = item->sizeHint(); // The suggested size for the current item + const QSize itemSize = item->sizeHint(); + // No leading space for the first item in a column. + const int spaceY = colItems.isEmpty() ? 0 : verticalSpacing(); - // Check if the current item fits in the remaining height of the current column - if (currentYPosition + itemSize.height() > availableHeight) { - // If not, layout the current column and start a new column - layoutSingleColumn(colItems, currentXPosition, originY); - colItems.clear(); // Reset the list for the new column - currentYPosition = originY; // Reset y-position to the column's start - currentXPosition += colWidth; // Move x-position to the next column - colWidth = 0; // Reset column width for the new column + if (!colItems.isEmpty() && colUsedHeight + spaceY + itemSize.height() > availableHeight) { + // Current item does not fit — flush the current column, begin a new one. + layoutSingleColumn(colItems, currentX, originY); + colItems.clear(); + currentX += colWidth + horizontalSpacing(); + colUsedHeight = 0; + colWidth = 0; } - // Add the item to the current column colItems.append(item); - colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item - currentYPosition += itemSize.height(); // Move y-position for the next item + colUsedHeight += (colItems.size() > 1 ? verticalSpacing() : 0) + itemSize.height(); + colWidth = qMax(colWidth, itemSize.width()); } - // Layout the final column if there are any remaining items - layoutSingleColumn(colItems, currentXPosition, originY); - - // Return the total width used, including the last column's width - return currentXPosition + colWidth; + layoutSingleColumn(colItems, currentX, originY); // Flush the final column. + return currentX + colWidth; } /** - * @brief Arranges a single column of items within specified x and y starting positions. - * @param colItems A list of items to be arranged in the column. - * @param x The starting x-coordinate for the column. - * @param y The starting y-coordinate for the column. + * @brief Sets the geometry for every item in @p colItems, starting at (@p x, @p y). + * + * Each item is placed at its sizeHint, clamped to its maximumSize. */ void FlowLayout::layoutSingleColumn(const QVector &colItems, const int x, int y) { for (QLayoutItem *item : colItems) { - if (item == nullptr) { - qCDebug(FlowLayoutLog) << "Item is null."; + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping null or empty item in column."; continue; } - if (item->isEmpty()) { - qCDebug(FlowLayoutLog) << "Skipping empty item."; - continue; - } - - // Debugging: Print the item's widget class name and size hint QWidget *widget = item->widget(); - if (widget) { - qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className(); - qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint(); - qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize(); - qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize(); - - // Debugging: Print child widgets - const QObjectList &children = widget->children(); - qCDebug(FlowLayoutLog) << "Child widgets:"; - for (QObject *child : children) { - if (QWidget *childWidget = qobject_cast(child)) { - qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className(); - qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint(); - qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize(); - } - } - } else { - qCDebug(FlowLayoutLog) << "Item does not have a widget."; + if (!widget) { + qCDebug(FlowLayoutLog) << "Item has no widget; skipping."; + continue; } - // Get the maximum allowed size for the item - QSize itemMaxSize = widget->maximumSize(); - // Constrain the item's width and height to its size hint or maximum size - const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); - const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); - // Debugging: Print the computed geometry - qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth - << ", height=" << itemHeight; + qCDebug(FlowLayoutLog) << "Widget:" << widget->metaObject()->className() << "sizeHint:" << widget->sizeHint() + << "maximumSize:" << widget->maximumSize() << "minimumSize:" << widget->minimumSize(); + + const QSize maxSize = widget->maximumSize(); + const int itemWidth = qMin(item->sizeHint().width(), maxSize.width()); + const int itemHeight = qMin(item->sizeHint().height(), maxSize.height()); + + qCDebug(FlowLayoutLog) << "Placing at x=" << x << "y=" << y << "w=" << itemWidth << "h=" << itemHeight; // Set the item's geometry based on the computed size and position item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); - - // Move the y-position down by the item's height to place the next item below - y += itemHeight; + y += itemHeight + verticalSpacing(); } } /** - * @brief Calculates the preferred size of the layout based on the flow direction. - * @return A QSize representing the ideal dimensions of the layout. - */ -QSize FlowLayout::sizeHint() const -{ - if (flowDirection == Qt::Horizontal) { - return calculateSizeHintHorizontal(); - } else { - return calculateSizeHintVertical(); - } -} - -/** - * @brief Calculates the minimum size required by the layout based on the flow direction. - * @return A QSize representing the minimum required dimensions. - */ -QSize FlowLayout::minimumSize() const -{ - if (flowDirection == Qt::Horizontal) { - return calculateMinimumSizeHorizontal(); - } else { - return calculateMinimumSizeVertical(); - } -} - -/** - * @brief Calculates the size hint for horizontal flow direction. - * @return A QSize representing the preferred dimensions. + * @brief Preferred size for horizontal flow: all items in a single row (unconstrained). + * + * The actual displayed height is determined by heightForWidth() once Qt knows the + * real available width. */ QSize FlowLayout::calculateSizeHintHorizontal() const { - int maxWidth = 0; // Tracks the maximum width needed - int totalHeight = 0; // Tracks the total height across all rows - int rowHeight = 0; // Tracks the height of the current row - int currentWidth = 0; // Tracks the current row's width - - const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth(); - - qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth; + int totalWidth = 0; + int maxHeight = 0; for (const QLayoutItem *item : items) { if (!item || item->isEmpty()) { - qCDebug(FlowLayoutLog) << "Skipping empty item."; continue; } - - QSize itemSize = item->sizeHint(); - int itemWidth = itemSize.width() + horizontalSpacing(); - qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth; - - if (currentWidth + itemWidth > availableWidth) { - qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight; - maxWidth = qMax(maxWidth, currentWidth); - totalHeight += rowHeight + verticalSpacing(); - qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth; - - currentWidth = 0; - rowHeight = 0; + const QSize s = item->sizeHint(); + if (totalWidth > 0) { + totalWidth += horizontalSpacing(); } - - currentWidth += itemWidth; - rowHeight = qMax(rowHeight, itemSize.height()); - qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight; + totalWidth += s.width(); + maxHeight = qMax(maxHeight, s.height()); } - - // Account for the final row - maxWidth = qMax(maxWidth, currentWidth); - totalHeight += rowHeight; - qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth; - - return QSize(maxWidth, totalHeight); + return QSize(totalWidth, maxHeight); } /** - * @brief Calculates the minimum size for horizontal flow direction. - * @return A QSize representing the minimum required dimensions. + * @brief Minimum size for horizontal flow: the largest single item. + * + * This guarantees we can always display at least one item per row. */ QSize FlowLayout::calculateMinimumSizeHorizontal() const { - int maxWidth = 0; // Tracks the maximum width of a row - int totalHeight = 0; // Tracks the total height across all rows - int rowHeight = 0; // Tracks the height of the current row - int currentWidth = 0; // Tracks the current row's width + QSize size(0, 0); + for (const QLayoutItem *item : items) { + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; + } + size = size.expandedTo(item->minimumSize()); + } + return size; +} - const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth(); - - qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth; +/** + * @brief Preferred size for vertical flow: all items in a single column (unconstrained). + */ +QSize FlowLayout::calculateSizeHintVertical() const +{ + int maxWidth = 0; + int totalHeight = 0; for (const QLayoutItem *item : items) { if (!item || item->isEmpty()) { qCDebug(FlowLayoutLog) << "Skipping empty item."; continue; } - - QSize itemMinSize = item->minimumSize(); - int itemWidth = itemMinSize.width() + horizontalSpacing(); - qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth; - - if (currentWidth + itemWidth > availableWidth) { - qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight; - maxWidth = qMax(maxWidth, currentWidth); - totalHeight += rowHeight + verticalSpacing(); - qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth; - - currentWidth = 0; - rowHeight = 0; + const QSize s = item->sizeHint(); + if (totalHeight > 0) { + totalHeight += verticalSpacing(); } - - currentWidth += itemWidth; - rowHeight = qMax(rowHeight, itemMinSize.height()); - qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight; + totalHeight += s.height(); + maxWidth = qMax(maxWidth, s.width()); } - - // Account for the final row - maxWidth = qMax(maxWidth, currentWidth); - totalHeight += rowHeight; - qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth; - return QSize(maxWidth, totalHeight); } /** - * @brief Calculates the size hint for vertical flow direction. - * @return A QSize representing the preferred dimensions. - */ -QSize FlowLayout::calculateSizeHintVertical() const -{ - int totalWidth = 0; - int maxHeight = 0; - int colWidth = 0; - int currentHeight = 0; - - const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight()); - - qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight; - - for (const QLayoutItem *item : items) { - if (!item || item->isEmpty()) { - qCDebug(FlowLayoutLog) << "Skipping empty item."; - continue; - } - - QSize itemSize = item->sizeHint(); - qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize; - - if (currentHeight + itemSize.height() > availableHeight) { - qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight - << "Column width:" << colWidth; - totalWidth += colWidth + horizontalSpacing(); - maxHeight = qMax(maxHeight, currentHeight); - qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight; - - currentHeight = 0; - colWidth = 0; - } - - currentHeight += itemSize.height() + verticalSpacing(); - colWidth = qMax(colWidth, itemSize.width()); - qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth; - } - - // Account for the final column - totalWidth += colWidth; - maxHeight = qMax(maxHeight, currentHeight); - qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight; - - return QSize(totalWidth, maxHeight); -} - -/** - * @brief Calculates the minimum size for vertical flow direction. - * @return A QSize representing the minimum required dimensions. + * @brief Minimum size for vertical flow: the largest single item. */ QSize FlowLayout::calculateMinimumSizeVertical() const { - int totalWidth = 0; // Tracks the total width across all columns - int maxHeight = 0; // Tracks the maximum height of a column - int colWidth = 0; // Tracks the width of the current column - int currentHeight = 0; // Tracks the current column's height - - const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight()); - - qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight; - + QSize size(0, 0); for (const QLayoutItem *item : items) { if (!item || item->isEmpty()) { qCDebug(FlowLayoutLog) << "Skipping empty item."; continue; } - - QSize itemMinSize = item->minimumSize(); - int itemHeight = itemMinSize.height() + verticalSpacing(); - qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize - << "Height with spacing:" << itemHeight; - - if (currentHeight + itemHeight > availableHeight) { - qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight - << "Column width:" << colWidth; - totalWidth += colWidth + horizontalSpacing(); - maxHeight = qMax(maxHeight, currentHeight); - qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight; - - currentHeight = 0; - colWidth = 0; - } - - currentHeight += itemHeight; - colWidth = qMax(colWidth, itemMinSize.width()); - qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth; + size = size.expandedTo(item->minimumSize()); } - - // Account for the final column - totalWidth += colWidth; - maxHeight = qMax(maxHeight, currentHeight); - qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight; - - return QSize(totalWidth, maxHeight); + return size; } /** @@ -543,7 +397,7 @@ QSize FlowLayout::calculateMinimumSizeVertical() const */ void FlowLayout::addItem(QLayoutItem *item) { - if (item != nullptr) { + if (item) { items.append(item); } } @@ -551,11 +405,8 @@ void FlowLayout::addItem(QLayoutItem *item) void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index) { addChildWidget(toInsert); - - // We don't want to fail on an index that violates the bounds, so we just clamp it. - int boundedIndex = qBound(0, index, qMax(0, static_cast(items.size()))); - items.insert(boundedIndex, new QWidgetItem(toInsert)); - + const int bounded = qBound(0, index, static_cast(items.size())); + items.insert(bounded, new QWidgetItem(toInsert)); invalidate(); } @@ -613,52 +464,13 @@ int FlowLayout::verticalSpacing() const */ int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const { - QObject *parent = this->parent(); - - if (!parent) { + QObject *p = parent(); + if (!p) { return -1; } - - if (parent->isWidgetType()) { - const auto *pw = dynamic_cast(parent); + if (p->isWidgetType()) { + const auto *pw = static_cast(p); return pw->style()->pixelMetric(pm, nullptr, pw); } - - return dynamic_cast(parent)->spacing(); -} - -/** - * @brief Gets the width of the parent scroll area, if any. - * @return The width of the scroll area's viewport, or 0 if not found. - */ -int FlowLayout::getParentScrollAreaWidth() const -{ - QWidget *parent = parentWidget(); - - while (parent) { - if (const auto *scrollArea = qobject_cast(parent)) { - return scrollArea->viewport()->width(); - } - parent = parent->parentWidget(); - } - - return 0; -} - -/** - * @brief Gets the height of the parent scroll area, if any. - * @return The height of the scroll area's viewport, or 0 if not found. - */ -int FlowLayout::getParentScrollAreaHeight() const -{ - QWidget *parent = parentWidget(); - - while (parent) { - if (const auto *scrollArea = qobject_cast(parent)) { - return scrollArea->viewport()->height(); - } - parent = parent->parentWidget(); - } - - return 0; -} + return static_cast(p)->spacing(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/layouts/flow_layout.h b/cockatrice/src/interface/layouts/flow_layout.h index cf109d260..9adcd8bb5 100644 --- a/cockatrice/src/interface/layouts/flow_layout.h +++ b/cockatrice/src/interface/layouts/flow_layout.h @@ -1,8 +1,10 @@ /** * @file flow_layout.h * @ingroup UI - * @brief TODO: Document this. + * @brief A QLayout subclass that arranges child widgets in wrapping rows (horizontal flow) + * or wrapping columns (vertical flow). */ +//! \todo Document this file. #ifndef FLOW_LAYOUT_H #define FLOW_LAYOUT_H @@ -10,8 +12,8 @@ #include #include #include +#include #include -#include inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg); @@ -19,42 +21,55 @@ class FlowLayout : public QLayout { public: explicit FlowLayout(QWidget *parent = nullptr); - FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0); + FlowLayout(QWidget *parent, Qt::Orientation flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0); ~FlowLayout() override; + void insertWidgetAtIndex(QWidget *toInsert, int index); - [[nodiscard]] QSize calculateMinimumSizeHorizontal() const; - [[nodiscard]] QSize calculateSizeHintVertical() const; - [[nodiscard]] QSize calculateMinimumSizeVertical() const; + // QLayout interface void addItem(QLayoutItem *item) override; [[nodiscard]] int count() const override; [[nodiscard]] QLayoutItem *itemAt(int index) const override; QLayoutItem *takeAt(int index) override; - [[nodiscard]] int horizontalSpacing() const; + void setGeometry(const QRect &rect) override; + // Size negotiation [[nodiscard]] Qt::Orientations expandingDirections() const override; [[nodiscard]] bool hasHeightForWidth() const override; [[nodiscard]] int heightForWidth(int width) const override; - [[nodiscard]] int verticalSpacing() const; - [[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const; - [[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const; - [[nodiscard]] int getParentScrollAreaWidth() const; - [[nodiscard]] int getParentScrollAreaHeight() const; - - void setGeometry(const QRect &rect) override; - virtual int layoutAllRows(int originX, int originY, int availableWidth); - virtual void layoutSingleRow(const QVector &rowItems, int x, int y); - int layoutAllColumns(int originX, int originY, int availableHeight); - void layoutSingleColumn(const QVector &colItems, int x, int y); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSize() const override; - [[nodiscard]] QSize calculateSizeHintHorizontal() const; + + // Spacing helpers + void setHorizontalMargin(int margin) + { + horizontalMargin = margin; + } + [[nodiscard]] int horizontalSpacing() const; + void setVerticalMargin(int margin) + { + verticalMargin = margin; + } + [[nodiscard]] int verticalSpacing() const; + [[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const; + + // Layout passes (virtual so subclasses can override placement logic) + virtual int layoutAllRows(int originX, int originY, int availableWidth); + virtual void layoutSingleRow(const QVector &rowItems, int x, int y, int availableWidth); + int layoutAllColumns(int originX, int originY, int availableHeight); + void layoutSingleColumn(const QVector &colItems, int x, int y); protected: - QList items; // List to store layout items + // Size-hint helpers split by direction + [[nodiscard]] QSize calculateSizeHintHorizontal() const; + [[nodiscard]] QSize calculateMinimumSizeHorizontal() const; + [[nodiscard]] QSize calculateSizeHintVertical() const; + [[nodiscard]] QSize calculateMinimumSizeVertical() const; + + QList items; Qt::Orientation flowDirection; - int horizontalMargin; - int verticalMargin; + int horizontalMargin; ///< Horizontal spacing between items (-1 = use style default) + int verticalMargin; ///< Vertical spacing between items (-1 = use style default) }; #endif // FLOW_LAYOUT_H \ No newline at end of file diff --git a/cockatrice/src/interface/layouts/overlap_layout.cpp b/cockatrice/src/interface/layouts/overlap_layout.cpp index 9bf5e8468..a4c13518a 100644 --- a/cockatrice/src/interface/layouts/overlap_layout.cpp +++ b/cockatrice/src/interface/layouts/overlap_layout.cpp @@ -223,7 +223,7 @@ void OverlapLayout::setGeometry(const QRect &rect) const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight); item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight)); - // TODO: Figure this out properly or maybe adjust size hint to account for this? + //! \todo Figure this out properly or maybe adjust size hint to account for this. // Update row and column indices based on the layout direction. if (overlapDirection == Qt::Horizontal) { currentColumn++; diff --git a/cockatrice/src/interface/layouts/overlap_layout.h b/cockatrice/src/interface/layouts/overlap_layout.h index 5844fca41..c1b6a8e51 100644 --- a/cockatrice/src/interface/layouts/overlap_layout.h +++ b/cockatrice/src/interface/layouts/overlap_layout.h @@ -1,8 +1,8 @@ /** * @file overlap_layout.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef OVERLAP_LAYOUT_H #define OVERLAP_LAYOUT_H diff --git a/cockatrice/src/interface/logger.cpp b/cockatrice/src/interface/logger.cpp index d6df065e8..3e00a0b24 100644 --- a/cockatrice/src/interface/logger.cpp +++ b/cockatrice/src/interface/logger.cpp @@ -66,8 +66,9 @@ void Logger::openLogfileSession() void Logger::closeLogfileSession() { - if (!logToFileEnabled) + if (!logToFileEnabled) { return; + } logToFileEnabled = false; fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl; diff --git a/cockatrice/src/interface/logger.h b/cockatrice/src/interface/logger.h index 59244e226..fc6dd70be 100644 --- a/cockatrice/src/interface/logger.h +++ b/cockatrice/src/interface/logger.h @@ -1,8 +1,8 @@ /** * @file logger.h * @ingroup Core - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LOGGER_H #define LOGGER_H diff --git a/cockatrice/src/interface/palette_editor/color_button.cpp b/cockatrice/src/interface/palette_editor/color_button.cpp new file mode 100644 index 000000000..e1a490d20 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/color_button.cpp @@ -0,0 +1,72 @@ +#include "color_button.h" + +#include +#include + +ColorButton::ColorButton(QWidget *parent) : QToolButton(parent) +{ + setFixedSize(52, 24); + setCursor(Qt::PointingHandCursor); + setToolTip(tr("Click to pick a color")); + connect(this, &QToolButton::clicked, this, &ColorButton::pickColor); +} + +void ColorButton::setColor(const QColor &c) +{ + if (color == c) { + return; + } + color = c; + updateSwatch(); + emit colorChanged(c); +} + +void ColorButton::pickColor() +{ + QColor chosen = QColorDialog::getColor(color, this, tr("Pick colour"), QColorDialog::ShowAlphaChannel); + if (chosen.isValid()) { + setColor(chosen); + } +} + +void ColorButton::updateSwatch() +{ + QPixmap pixmap(size() * devicePixelRatioF()); + pixmap.setDevicePixelRatio(devicePixelRatioF()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing); + + // Checkerboard for alpha + const int cellSize = 4; + for (int y = 0; y < height(); y += cellSize) { + for (int x = 0; x < width(); x += cellSize) { + painter.fillRect(x, y, cellSize, cellSize, + ((x / cellSize + y / cellSize) % 2) ? QColor(180, 180, 180) : Qt::white); + } + } + + // Color fill + painter.setPen(Qt::NoPen); + painter.setBrush(color); + painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3); + + // Border + QColor border = palette().color(QPalette::Shadow); + border.setAlpha(180); + painter.setPen(QPen(border, 1)); + painter.setBrush(Qt::NoBrush); + painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3); + + // Hex label — black or white for contrast + int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue(); + painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200)); + QFont f = font(); + f.setPixelSize(8); + painter.setFont(f); + painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper()); + + setIcon(QIcon(pixmap)); + setIconSize(size()); + setText({}); +} \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/color_button.h b/cockatrice/src/interface/palette_editor/color_button.h new file mode 100644 index 000000000..4b139ef68 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/color_button.h @@ -0,0 +1,30 @@ +#ifndef COCKATRICE_COLOR_BUTTON_H +#define COCKATRICE_COLOR_BUTTON_H + +#include +#include + +class ColorButton : public QToolButton +{ + Q_OBJECT +public: + explicit ColorButton(QWidget *parent = nullptr); + + QColor getColor() const + { + return color; + } + void setColor(const QColor &c); + +signals: + void colorChanged(const QColor &color); + +private slots: + void pickColor(); + +private: + void updateSwatch(); + QColor color; +}; + +#endif // COCKATRICE_COLOR_BUTTON_H \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp new file mode 100644 index 000000000..1b9be1dd4 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -0,0 +1,326 @@ +#include "palette_editor_dialog.h" + +#include "../theme_manager.h" +#include "palette_generator.h" +#include "palette_grid_widget.h" +#include "quick_setup_panel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent) + : QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName) +{ + setMinimumSize(740, 220); + setupUi(); + + // Load both scheme configs upfront so switching is instant + loadSchemes(); + + loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; + + schemeComboBox->blockSignals(true); + schemeComboBox->setCurrentText(loadedScheme); + schemeComboBox->blockSignals(false); + + paletteGrid->loadPalette(workingConfig[loadedScheme]); + seedAccentFromScheme(loadedScheme); + + retranslateUi(); +} + +void PaletteEditorDialog::setupUi() +{ + auto *root = new QVBoxLayout(this); + root->setSpacing(0); + root->setContentsMargins(0, 0, 0, 0); + + // Header + header = new QWidget; + header->setAutoFillBackground(true); + { + QPalette hp = header->palette(); + hp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(108)); + header->setPalette(hp); + } + auto *headerLayout = new QHBoxLayout(header); + headerLayout->setContentsMargins(12, 8, 12, 8); + + titleLabel = new QLabel(this); + titleLabel->setTextFormat(Qt::RichText); + + editingLabel = new QLabel(this); + + schemeComboBox = new QComboBox; + schemeComboBox->addItems({"Light", "Dark"}); + schemeComboBox->setFixedWidth(90); + + headerLayout->addWidget(titleLabel); + headerLayout->addStretch(); + headerLayout->addWidget(editingLabel); + headerLayout->addWidget(schemeComboBox); + root->addWidget(header); + + auto makeSeparator = [&]() { + auto *sep = new QFrame; + sep->setFrameShape(QFrame::HLine); + sep->setFrameShadow(QFrame::Plain); + sep->setFixedHeight(1); + return sep; + }; + + root->addWidget(makeSeparator()); + + // Quick Setup panel + quickSetupPanel = new QuickSetupPanel; + quickSetupPanel->setAutoFillBackground(true); + + QPalette sp = quickSetupPanel->palette(); + sp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(102)); + quickSetupPanel->setPalette(sp); + + root->addWidget(quickSetupPanel); + root->addWidget(makeSeparator()); + + // Toggle button — acts as a section header for the advanced area + paletteGridToggleButton = new QPushButton(this); + paletteGridToggleButton->setCheckable(true); + paletteGridToggleButton->setChecked(false); + paletteGridToggleButton->setFlat(true); + paletteGridToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }" + "QPushButton:checked { }"); + root->addWidget(paletteGridToggleButton); + + // Separator + grid start hidden; revealed by the toggle + paletteGridSeparator = makeSeparator(); + paletteGridSeparator->setVisible(false); + root->addWidget(paletteGridSeparator); + + paletteGrid = new PaletteGridWidget; + paletteGrid->setVisible(false); + root->addWidget(paletteGrid, 1); + + // Footer + root->addWidget(makeSeparator()); + footer = new QWidget; + footer->setAutoFillBackground(true); + { + QPalette fp = footer->palette(); + fp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(104)); + footer->setPalette(fp); + } + auto *footerLayout = new QHBoxLayout(footer); + footerLayout->setContentsMargins(12, 8, 12, 8); + + revertButton = new QPushButton(this); + + buttonBox = new QDialogButtonBox; + resetBtn = buttonBox->addButton(tr("Reset"), QDialogButtonBox::ResetRole); + applyBtn = buttonBox->addButton(tr("Apply"), QDialogButtonBox::ApplyRole); + saveBtn = buttonBox->addButton(tr("Save && Apply"), QDialogButtonBox::AcceptRole); + closeBtn = buttonBox->addButton(QDialogButtonBox::Close); + + footerLayout->addWidget(revertButton); + footerLayout->addStretch(); + footerLayout->addWidget(buttonBox); + root->addWidget(footer); + + // Connections + connect(schemeComboBox, &QComboBox::currentTextChanged, this, &PaletteEditorDialog::onSchemeChanged); + connect(quickSetupPanel, &QuickSetupPanel::generateRequested, this, &PaletteEditorDialog::onGenerateFromAccent); + connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault); + connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset); + connect(applyBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onApply); + connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave); + connect(closeBtn, &QPushButton::clicked, this, &QDialog::reject); + + connect(paletteGridToggleButton, &QPushButton::toggled, this, [this](bool open) { + paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette")); + paletteGridSeparator->setVisible(open); + paletteGrid->setVisible(open); + + if (open) { + setMinimumHeight(680); + resize(width(), 680); + } else { + setMinimumHeight(220); + adjustSize(); // shrinks to fit just the visible content + } + }); +} + +void PaletteEditorDialog::retranslateUi() +{ + setWindowTitle(tr("Palette Editor — %1").arg(themeName)); + titleLabel->setText(tr("Palette Editor  ·  %1").arg(themeName)); + + // Revert button only makes sense when the theme ships default palette files + const bool hasDefault = PaletteConfig::fromDefault(themeDirPath, "Light").hasPalette() || + PaletteConfig::fromDefault(themeDirPath, "Dark").hasPalette(); + revertButton->setEnabled(hasDefault); + if (!hasDefault) { + revertButton->setToolTip(tr("This theme ships no default palette files")); + } else { + revertButton->setToolTip(tr("Replace current colours with the theme author's defaults")); + } + + schemeComboBox->setToolTip(tr("Switch between the light and dark palette files")); + editingLabel->setText(tr("Editing:")); + paletteGridToggleButton->setText(tr("▶ Edit Palette")); + paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks")); + revertButton->setText(tr("↺ Revert to theme default")); + + resetBtn->setText(tr("Reset")); + applyBtn->setText(tr("Apply")); + saveBtn->setText(tr("Save && Apply")); + resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); + applyBtn->setToolTip(tr("Preview this palette without saving to disk")); + saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); + + if (themeDirPath.isEmpty()) { + saveBtn->setEnabled(false); + saveBtn->setToolTip(tr("Cannot save: this theme has no directory on disk")); + } +} + +void PaletteEditorDialog::loadSchemes() +{ + const QStringList schemes = {"Light", "Dark"}; + for (const QString &scheme : schemes) { + PaletteConfig cfg = PaletteConfig::fromScheme(themeDirPath, scheme); + + if (!cfg.hasPalette()) { + cfg = PaletteConfig::fromDefault(themeDirPath, scheme); + } + + if (!cfg.hasPalette()) { + const QPalette appPal = qApp->palette(); + for (auto group : {QPalette::Active, QPalette::Disabled, QPalette::Inactive}) { + for (int i = 0; i < QPalette::NColorRoles; ++i) { + auto role = static_cast(i); + if (role != QPalette::NoRole) { + cfg.colors[group][role] = appPal.color(group, role); + } + } + } + } + savedConfig[scheme] = cfg; + workingConfig[scheme] = cfg; + } +} + +void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) +{ + QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight); + if (seed.isValid()) { + quickSetupPanel->setAccentColor(seed); + } +} + +void PaletteEditorDialog::onSchemeChanged(const QString &scheme) +{ + // Snapshot unsaved edits for the scheme we're leaving + if (!loadedScheme.isEmpty()) { + workingConfig[loadedScheme] = paletteGrid->currentPaletteConfig(); + } + + loadedScheme = scheme; + paletteGrid->loadPalette(workingConfig.value(scheme)); + seedAccentFromScheme(scheme); + onApply(); +} + +void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensity) +{ + PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, loadedScheme); + workingConfig[loadedScheme] = cfg; + paletteGrid->loadPalette(cfg); +} + +void PaletteEditorDialog::onApply() +{ + themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); +} + +void PaletteEditorDialog::onSave() +{ + if (loadedScheme.isEmpty()) { + return; + } + + PaletteConfig cfg = paletteGrid->currentPaletteConfig(); + + if (!ThemeManager::savePaletteConfig(themeDirPath, loadedScheme, cfg)) { + QMessageBox::warning(this, tr("Save failed"), + tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), themeDirPath)); + return; + } + + ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath); + globalCfg.colorScheme = loadedScheme; + globalCfg.save(themeDirPath); + + savedConfig[loadedScheme] = cfg; + workingConfig[loadedScheme] = cfg; + themeManager->reloadCurrentTheme(); + accept(); +} + +void PaletteEditorDialog::onReset() +{ + workingConfig[loadedScheme] = savedConfig[loadedScheme]; + paletteGrid->loadPalette(savedConfig[loadedScheme]); +} + +void PaletteEditorDialog::onRevertToDefault() +{ + PaletteConfig def = PaletteConfig::fromDefault(themeDirPath, loadedScheme); + if (!def.hasPalette()) { + QMessageBox::information(this, tr("No default found"), + tr("No default palette file found for the \"%1\" scheme.").arg(loadedScheme)); + return; + } + workingConfig[loadedScheme] = def; + paletteGrid->loadPalette(def); +} + +void PaletteEditorDialog::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::PaletteChange) { + QTimer::singleShot(0, this, &PaletteEditorDialog::refreshChromePalettes); + } + + QDialog::changeEvent(e); +} + +void PaletteEditorDialog::refreshChromePalettes() +{ + const QPalette base = qApp->palette(); + + if (header) { + QPalette hp = header->palette(); + hp.setColor(QPalette::Window, base.color(QPalette::Window).darker(108)); + header->setPalette(hp); + header->update(); + } + if (footer) { + QPalette fp = footer->palette(); + fp.setColor(QPalette::Window, base.color(QPalette::Window).darker(104)); + footer->setPalette(fp); + footer->update(); + } + if (quickSetupPanel) { + QPalette sp = quickSetupPanel->palette(); + sp.setColor(QPalette::Window, base.color(QPalette::Window).darker(102)); + quickSetupPanel->setPalette(sp); + quickSetupPanel->update(); + } +} diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h new file mode 100644 index 000000000..cec4c1700 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h @@ -0,0 +1,68 @@ +#ifndef COCKATRICE_PALETTE_EDITOR_DIALOG_H +#define COCKATRICE_PALETTE_EDITOR_DIALOG_H + +#include "../theme_config.h" + +#include +#include +#include + +class QLabel; +class QComboBox; +class QDialogButtonBox; +class QPushButton; +class PaletteGridWidget; +class QuickSetupPanel; + +class PaletteEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr); + void loadSchemes(); + +private slots: + void onSave(); + void onApply(); + void onReset(); + void onRevertToDefault(); + void onSchemeChanged(const QString &scheme); + void onGenerateFromAccent(const QColor &accent, int intensity); + +private: + void setupUi(); + void retranslateUi(); + void refreshChromePalettes(); + void loadScheme(const QString &scheme); // snapshot current, switch, load + void seedAccentFromScheme(const QString &scheme); + + // Sub-widgets + QWidget *header; + QLabel *titleLabel; + QLabel *editingLabel; + QuickSetupPanel *quickSetupPanel = nullptr; + PaletteGridWidget *paletteGrid = nullptr; + QPushButton *paletteGridToggleButton = nullptr; + QFrame *paletteGridSeparator = nullptr; + QWidget *footer; + QComboBox *schemeComboBox = nullptr; + QDialogButtonBox *buttonBox = nullptr; + QPushButton *resetBtn = nullptr; + QPushButton *applyBtn = nullptr; + QPushButton *saveBtn = nullptr; + QPushButton *closeBtn = nullptr; + QPushButton *revertButton = nullptr; + + // State + QString themeDirPath; + QString themeName; + QString loadedScheme; + + QMap workingConfig; + QMap savedConfig; + +protected: + void changeEvent(QEvent *e) override; +}; + +#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_generator.cpp b/cockatrice/src/interface/palette_editor/palette_generator.cpp new file mode 100644 index 000000000..d30dd14f1 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_generator.cpp @@ -0,0 +1,200 @@ +#include "palette_generator.h" + +#include + +// ════════════════════════════════════════════════════════════════════════════ +// PaletteGenerator::fromAccent +// +// Three intensity bands: +// 0–30 Subtle — Highlight / links / BrightText take on the hue; +// backgrounds stay neutral grey. +// 30–70 Accented — Button, ToolTipBase, AlternateBase, shading tint in; +// backgrounds get a faint hue wash. +// 70–100 Chromatic— Window and Base pick up real saturation; the whole +// application reads as that colour, text stays readable. +// +// Key principles: +// • Quadratic saturation curve: low end feels truly subtle, top is vivid. +// • Each role has its own saturation ceiling; buttons always pop above window. +// • Base stays near-white / near-black regardless of intensity for legibility. +// • Button text contrast is computed from the real button color, not assumed. +// • Tooltip blends from classic yellow to hue-tinted above intensity ~25. +// • 3D shading ladder (Light→Shadow) carries the same hue for coherence. +// • Disabled: text → mid-gray, backgrounds → match Window. +// • Inactive Highlight fades to a near-bg tone so it doesn't compete. +// ════════════════════════════════════════════════════════════════════════════ + +namespace PaletteGenerator +{ + +PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme) +{ + PaletteConfig cfg; + const bool dark = scheme.compare("Dark", Qt::CaseInsensitive) == 0; + const double t = intensity / 100.0; // 0.0 – 1.0 + + int h = accent.hslHue(); + const bool achromatic = (h < 0); + if (achromatic) { + h = 0; + } + + // Saturation budgets + // Quadratic ease-in means the subtle end is genuinely subtle and the + // full end is bold without being garish. + auto sat = [&](double maxSat) -> int { + if (achromatic) { + return 0; + } + return qRound(maxSat * t * t); + }; + + const int satWindow = sat(dark ? 80.0 : 90.0); + const int satBase = sat(dark ? 25.0 : 20.0); // text areas stay near-white/black + const int satAlt = sat(dark ? 90.0 : 100.0); + const int satButton = sat(dark ? 120.0 : 130.0); // buttons pop above the bg + const int satTooltip = sat(dark ? 90.0 : 80.0); + const int satHighlight = achromatic ? 0 : qRound(accent.hslSaturation() * (0.45 + 0.55 * t)); + const int satShadeHi = sat(dark ? 60.0 : 50.0); // Light / Midlight + const int satShadeLo = sat(dark ? 90.0 : 70.0); // Mid / Dark + + // Per-role lightness + // Nudge lightness slightly as saturation rises to compensate for the + // Helmholtz-Kohlrausch effect (saturated colors look lighter/heavier). + const int winL = dark ? (28 + qRound(t * 8)) : (242 - qRound(t * 6)); + const int baseL = dark ? (43 + qRound(t * 6)) : 252; + const int altL = dark ? (36 + qRound(t * 9)) : (234 - qRound(t * 5)); + const int btnL = dark ? (56 + qRound(t * 10)) : (230 - qRound(t * 10)); + const int tipL = dark ? (52 + qRound(t * 8)) : (248 - qRound(t * 6)); + + // Highlight color + QColor hl; + if (achromatic) { + hl = dark ? QColor(105, 105, 105) : QColor(95, 95, 95); + } else if (dark) { + int L = qBound(105, accent.lightness() + qRound(45.0 * (1.0 - t)), 215); + hl = QColor::fromHsl(h, qMin(255, satHighlight), L); + } else { + int L = qBound(50, accent.lightness() - qRound(25.0 * t), 180); + hl = QColor::fromHsl(h, qMin(255, satHighlight), L); + } + const double hlLuma = 0.299 * hl.red() + 0.587 * hl.green() + 0.114 * hl.blue(); + const QColor hlText = (hlLuma > 135) ? Qt::black : Qt::white; + + // Local helpers + using CR = QPalette::ColorRole; + using CG = QPalette::ColorGroup; + + auto hsl = [&](int lightness, int s) -> QColor { + return QColor::fromHsl(h, qBound(0, s, 255), qBound(0, lightness, 255)); + }; + + auto textOn = [](const QColor &bg) -> QColor { + double luma = 0.299 * bg.red() + 0.587 * bg.green() + 0.114 * bg.blue(); + return (luma > 135) ? Qt::black : Qt::white; + }; + + auto set3 = [&](CR role, QColor active, QColor disabled, QColor inactive) { + cfg.colors[CG::Active][role] = active; + cfg.colors[CG::Disabled][role] = disabled; + cfg.colors[CG::Inactive][role] = inactive; + }; + + auto setAll = [&](CR role, QColor c) { set3(role, c, c, c); }; + + // Tooltip: blend classic yellow → hue-tinted above t≈0.20 + QColor bg_tip; + if (achromatic || t < 0.20) { + bg_tip = QColor(255, 255, 220); + } else { + QColor tinted = hsl(tipL, satTooltip); + double blend = qMin(1.0, (t - 0.20) / 0.55); + QColor yellow(255, 255, 220); + bg_tip = QColor(qRound(yellow.red() * (1.0 - blend) + tinted.red() * blend), + qRound(yellow.green() * (1.0 - blend) + tinted.green() * blend), + qRound(yellow.blue() * (1.0 - blend) + tinted.blue() * blend)); + } + + // Backgrounds + const QColor bg_win = hsl(winL, satWindow); + const QColor bg_base = hsl(baseL, satBase); + const QColor bg_alt = hsl(altL, satAlt); + const QColor bg_btn = hsl(btnL, satButton); + + set3(CR::Window, bg_win, bg_win, bg_win); + set3(CR::Base, bg_base, bg_win, bg_base); + set3(CR::AlternateBase, bg_alt, bg_alt, bg_alt); + set3(CR::Button, bg_btn, bg_win, bg_btn); + set3(CR::ToolTipBase, bg_tip, bg_tip, bg_tip); + + // Foreground text + const QColor winText = dark ? Qt::white : Qt::black; + const QColor disText = dark ? QColor(157, 157, 157) : QColor(120, 120, 120); + const QColor disBtnText = dark ? QColor(120, 120, 120) : QColor(150, 150, 150); + + set3(CR::WindowText, winText, disText, winText); + set3(CR::Text, winText, disText, winText); + set3(CR::ButtonText, textOn(bg_btn), disBtnText, textOn(bg_btn)); + setAll(CR::ToolTipText, textOn(bg_tip)); + + const QColor phAlpha = dark ? QColor(255, 255, 255, 110) : QColor(0, 0, 0, 110); + const QColor phDis = dark ? QColor(255, 255, 255, 70) : QColor(0, 0, 0, 70); + set3(CR::PlaceholderText, phAlpha, phDis, phAlpha); + + // Highlight / selection + const QColor inactiveHl = hsl(winL + (dark ? 14 : -10), satWindow); + cfg.colors[CG::Active][CR::Highlight] = hl; + cfg.colors[CG::Disabled][CR::Highlight] = inactiveHl; + cfg.colors[CG::Inactive][CR::Highlight] = inactiveHl; + cfg.colors[CG::Active][CR::HighlightedText] = hlText; + cfg.colors[CG::Disabled][CR::HighlightedText] = disText; + cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black; + + // BrightText + QColor bright; + if (achromatic) { + bright = dark ? Qt::white : Qt::black; + } else if (dark) { + bright = QColor::fromHsl(h, qMin(255, satHighlight + 25), qMin(235, hl.lightness() + 50)); + } else { + bright = Qt::white; + } + setAll(CR::BrightText, bright); + + // Links + QColor link, linkV; + if (achromatic) { + link = dark ? QColor(100, 200, 255) : QColor(0, 0, 210); + linkV = dark ? QColor(200, 100, 255) : QColor(128, 0, 180); + } else if (dark) { + link = QColor::fromHsl(h, qMin(255, satHighlight), qMin(230, hl.lightness() + 75)); + linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMin(215, hl.lightness() + 55)); + } else { + link = QColor::fromHsl(h, qMin(255, satHighlight), qMax(40, hl.lightness() - 75)); + linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMax(30, hl.lightness() - 95)); + } + set3(CR::Link, link, dark ? QColor(48, 140, 198) : QColor(0, 0, 255), link); + set3(CR::LinkVisited, linkV, dark ? QColor(180, 80, 255) : QColor(255, 0, 255), linkV); + + // 3D / frame shading + if (dark) { + setAll(CR::Light, hsl(115, qMin(255, satShadeHi))); + setAll(CR::Midlight, hsl(82, qMin(255, satShadeHi))); + setAll(CR::Mid, hsl(37, satShadeLo)); + setAll(CR::Dark, hsl(22, satShadeLo)); + setAll(CR::Shadow, Qt::black); + } else { + setAll(CR::Light, Qt::white); + setAll(CR::Midlight, hsl(226, qMin(255, satShadeHi))); + setAll(CR::Mid, hsl(158, satShadeLo)); + setAll(CR::Dark, hsl(148, satShadeLo)); + // Shadow stays neutral — tinting it makes the UI look bruised + cfg.colors[CG::Active][CR::Shadow] = QColor(105, 105, 105); + cfg.colors[CG::Disabled][CR::Shadow] = Qt::black; + cfg.colors[CG::Inactive][CR::Shadow] = QColor(105, 105, 105); + } + + return cfg; +} + +} // namespace PaletteGenerator \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_generator.h b/cockatrice/src/interface/palette_editor/palette_generator.h new file mode 100644 index 000000000..0cec9b5c3 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_generator.h @@ -0,0 +1,16 @@ +#ifndef COCKATRICE_PALETTE_GENERATOR_H +#define COCKATRICE_PALETTE_GENERATOR_H + +#include "../theme_config.h" + +#include +#include + +// All QPalette roles are derived from a single accent color and an +// intensity value (0-100). See the .cpp for the full band breakdown. +namespace PaletteGenerator +{ +PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme); +} // namespace PaletteGenerator + +#endif // COCKATRICE_PALETTE_GENERATOR_H \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp new file mode 100644 index 000000000..e4ac5a16f --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -0,0 +1,179 @@ +#include "palette_grid_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +static QList allRoles() +{ + QList roles; + for (int i = 0; i < QPalette::NColorRoles; ++i) { + auto r = static_cast(i); + if (r != QPalette::NoRole) { + roles << r; + } + } + return roles; +} + +static const QList ALL_GROUPS = {QPalette::Active, QPalette::Disabled, QPalette::Inactive}; + +static const QMap ROLE_DESCRIPTIONS = { + {QPalette::Window, QT_TR_NOOP("Main window / dialog background")}, + {QPalette::WindowText, QT_TR_NOOP("Text drawn on Window")}, + {QPalette::Base, QT_TR_NOOP("Background for text input widgets")}, + {QPalette::Text, QT_TR_NOOP("Text in input widgets")}, + {QPalette::Button, QT_TR_NOOP("Button background")}, + {QPalette::ButtonText, QT_TR_NOOP("Button label text")}, + {QPalette::BrightText, QT_TR_NOOP("High-contrast text (e.g. checked items)")}, + {QPalette::Highlight, QT_TR_NOOP("Selection / focus highlight")}, + {QPalette::HighlightedText, QT_TR_NOOP("Text on top of Highlight")}, + {QPalette::Link, QT_TR_NOOP("Unvisited hyperlink")}, + {QPalette::LinkVisited, QT_TR_NOOP("Visited hyperlink")}, + {QPalette::AlternateBase, QT_TR_NOOP("Alternating row background in views")}, + {QPalette::ToolTipBase, QT_TR_NOOP("Tooltip background")}, + {QPalette::ToolTipText, QT_TR_NOOP("Tooltip text")}, + {QPalette::PlaceholderText, QT_TR_NOOP("Placeholder / hint text in inputs")}, + {QPalette::Light, QT_TR_NOOP("Lighter than Button (3-D highlight)")}, + {QPalette::Midlight, QT_TR_NOOP("Between Button and Light")}, + {QPalette::Mid, QT_TR_NOOP("Between Button and Dark")}, + {QPalette::Dark, QT_TR_NOOP("Darker than Button (3-D shadow)")}, + {QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")}, +}; + +PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) +{ + scroll = new QScrollArea(this); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + + gridHost = new QWidget; + buildGrid(gridHost); + refreshChromePalettes(); + scroll->setWidget(gridHost); + + layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(scroll); +} + +void PaletteGridWidget::buildGrid(QWidget *host) +{ + QMetaEnum roleEnum = QMetaEnum::fromType(); + + auto *grid = new QGridLayout(host); + grid->setSpacing(3); + grid->setContentsMargins(12, 8, 12, 8); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(1, 0); + grid->setColumnStretch(2, 0); + grid->setColumnStretch(3, 0); + + // Column headers + const QStringList groupHeaders = {tr("Active"), tr("Disabled"), tr("Inactive")}; + const QStringList groupTips = { + tr("Normal interactive state"), + tr("Widget is disabled / not interactive"), + tr("Window is in background / unfocused"), + }; + for (int col = 0; col < 3; ++col) { + auto *label = new QLabel(groupHeaders[col], host); + label->setAlignment(Qt::AlignCenter); + label->setToolTip(groupTips[col]); + QFont f = label->font(); + f.setBold(true); + label->setFont(f); + label->setAutoFillBackground(true); + label->setContentsMargins(4, 4, 4, 4); + grid->addWidget(label, 0, col + 1); + headerLabels.push_back(label); + } + + // Role rows + const auto roles = allRoles(); + for (int row = 0; row < roles.size(); ++row) { + auto role = roles[row]; + const char *name = roleEnum.valueToKey(role); + + // Alternating row shade + if (row % 2 == 0) { + for (int col = 0; col < 4; ++col) { + auto *shade = new QWidget(host); + shade->setAutoFillBackground(true); + grid->addWidget(shade, row + 1, col); + rowShadeWidgets.push_back(shade); + } + } + + auto *label = new QLabel(QString(name), host); + label->setToolTip(ROLE_DESCRIPTIONS.value(role, {})); + label->setContentsMargins(4, 2, 8, 2); + grid->addWidget(label, row + 1, 0); + + for (int col = 0; col < 3; ++col) { + auto group = ALL_GROUPS[col]; + auto *btn = new ColorButton(host); + colorButtons[group][role] = btn; + grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter); + } + } +} + +void PaletteGridWidget::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::PaletteChange) { + QTimer::singleShot(0, this, &PaletteGridWidget::refreshChromePalettes); + } + + QWidget::changeEvent(e); +} + +void PaletteGridWidget::refreshChromePalettes() +{ + const QPalette base = qApp->palette(); + const QColor alt = base.color(QPalette::AlternateBase); + + // Header labels + for (auto *label : headerLabels) { + QPalette lp = label->palette(); + lp.setColor(QPalette::Window, alt); + label->setPalette(lp); + label->update(); + } + + // Alternating row backgrounds + for (auto *shade : rowShadeWidgets) { + QPalette sp = shade->palette(); + sp.setColor(QPalette::Window, alt); + shade->setPalette(sp); + shade->update(); + } +} + +void PaletteGridWidget::loadPalette(const PaletteConfig &cfg) +{ + for (auto group : ALL_GROUPS) { + for (auto role : allRoles()) { + QColor color = cfg.colors.value(group).value(role); + if (!color.isValid()) { + color = qApp->palette().color(group, role); + } + colorButtons[group][role]->setColor(color); + } + } +} + +PaletteConfig PaletteGridWidget::currentPaletteConfig() const +{ + PaletteConfig cfg; + for (auto group : ALL_GROUPS) { + for (auto role : allRoles()) { + cfg.colors[group][role] = colorButtons[group][role]->getColor(); + } + } + return cfg; +} \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h new file mode 100644 index 000000000..1d85f1ac8 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -0,0 +1,38 @@ +#ifndef COCKATRICE_PALETTE_GRID_WIDGET_H +#define COCKATRICE_PALETTE_GRID_WIDGET_H + +#include "../theme_config.h" +#include "color_button.h" + +#include +#include +#include +#include + +class QLabel; +class QScrollArea; +// Scrollable grid of ColorButtons — one per (ColorGroup × ColorRole) cell. +// Owns the load/read round-trip for PaletteConfig but has no file I/O itself. +class PaletteGridWidget : public QWidget +{ + Q_OBJECT +public: + explicit PaletteGridWidget(QWidget *parent = nullptr); + + void loadPalette(const PaletteConfig &cfg); + PaletteConfig currentPaletteConfig() const; + +private: + void buildGrid(QWidget *host); + void changeEvent(QEvent *e); + void refreshChromePalettes(); + + QMap> colorButtons; + QScrollArea *scroll; + QWidget *gridHost; + QVBoxLayout *layout; + QVector headerLabels; + QVector rowShadeWidgets; +}; + +#endif // COCKATRICE_PALETTE_GRID_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp new file mode 100644 index 000000000..fe3af9b52 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp @@ -0,0 +1,99 @@ +#include "quick_setup_panel.h" + +#include +#include +#include +#include +#include + +QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(12, 8, 12, 8); + layout->setSpacing(10); + + heading = new QLabel(this); + heading->setTextFormat(Qt::RichText); + + accentLabel = new QLabel(this); + accentButton = new ColorButton(this); + accentButton->setColor(QColor(20, 140, 60)); + + intensityLabel = new QLabel(this); + + labelLow = new QLabel(this); + labelHigh = new QLabel(this); + QFont small = labelLow->font(); + small.setPointSizeF(small.pointSizeF() * 0.82); + labelLow->setFont(small); + labelHigh->setFont(small); + QPalette dimmed = labelLow->palette(); + dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid)); + labelLow->setPalette(dimmed); + labelHigh->setPalette(dimmed); + + intensitySlider = new QSlider(Qt::Horizontal, this); + intensitySlider->setRange(0, 100); + intensitySlider->setValue(70); + intensitySlider->setFixedWidth(160); + + intensityPercentageLabel = new QLabel(this); + intensityPercentageLabel->setFixedWidth(34); + intensityPercentageLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + + generateButton = new QPushButton(this); + + layout->addWidget(heading); + layout->addSpacing(6); + layout->addWidget(accentLabel); + layout->addWidget(accentButton); + layout->addSpacing(12); + layout->addWidget(intensityLabel); + layout->addWidget(labelLow); + layout->addWidget(intensitySlider); + layout->addWidget(labelHigh); + layout->addWidget(intensityPercentageLabel); + layout->addStretch(); + layout->addWidget(generateButton); + + connect(intensitySlider, &QSlider::valueChanged, this, + [this](int v) { intensityPercentageLabel->setText(tr("%1%").arg(v)); }); + connect(generateButton, &QPushButton::clicked, this, + [this] { emit generateRequested(accentButton->getColor(), intensitySlider->value()); }); + retranslateUi(); +} + +void QuickSetupPanel::retranslateUi() +{ + heading->setText(tr("Quick Setup")); + heading->setToolTip(tr("Generate all palette roles automatically from a single accent colour")); + accentLabel->setText(tr("Accent:")); + accentButton->setToolTip(tr("Primary hue. Used directly for highlights and links.\n" + "At high intensity it also tints buttons and backgrounds.")); + intensityLabel->setText(tr("Intensity:")); + labelLow->setText(tr("Subtle")); + labelHigh->setText(tr("Full colour")); + intensitySlider->setToolTip(tr("0–30 Subtle tint — only highlights and links change hue\n" + "30–70 Accented — buttons, tooltips, and borders join in\n" + "70–100 Full colour — backgrounds, everything")); + intensityPercentageLabel->setText(tr("70%")); + + generateButton->setText(tr("Generate ↓")); + generateButton->setToolTip(tr("Derive all palette roles from the accent colour above.\n" + "Fine-tune individual colours in the grid afterwards.")); +} + +QColor QuickSetupPanel::accentColor() const +{ + return accentButton->getColor(); +} + +int QuickSetupPanel::intensity() const +{ + return intensitySlider->value(); +} + +void QuickSetupPanel::setAccentColor(const QColor &c) +{ + accentButton->setColor(c); +} \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.h b/cockatrice/src/interface/palette_editor/quick_setup_panel.h new file mode 100644 index 000000000..dddd0aaa3 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.h @@ -0,0 +1,94 @@ +#ifndef COCKATRICE_QUICK_SETUP_PANEL_H +#define COCKATRICE_QUICK_SETUP_PANEL_H + +#include "color_button.h" + +#include + +class QPushButton; +class QHBoxLayout; +class QLabel; +class QSlider; + +/** + * @class QuickSetupPanel + * @brief Provides a compact "Quick Setup" interface for generating theme palettes. + * + * The panel contains: + * - an accent color picker, + * - an intensity slider, + * - and a generate button. + * + * When the user clicks the generate button, the panel emits + * generateRequested() with the currently selected accent color + * and intensity value. + * + * Typically used together with PaletteGenerator::fromAccent() + * to quickly generate color schemes from a chosen accent color. + */ +class QuickSetupPanel : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief Constructs the quick setup panel. + * + * @param parent Optional parent widget. + */ + explicit QuickSetupPanel(QWidget *parent = nullptr); + + /** + * @brief Retranslates all user-visible strings. + * + * Intended to be called when the application language changes. + */ + void retranslateUi(); + + /** + * @brief Returns the currently selected accent color. + * + * @return The selected accent color. + */ + QColor accentColor() const; + + /** + * @brief Returns the current intensity slider value. + * + * @return The selected intensity value. + */ + int intensity() const; + + /** + * @brief Updates the displayed accent color. + * + * Used by the parent dialog when switching schemes to keep + * the color swatch synchronized with the active palette. + * + * @param c The new accent color. + */ + void setAccentColor(const QColor &c); + +signals: + /** + * @brief Emitted when the user requests palette generation. + * + * @param accent The selected accent color. + * @param intensity The selected intensity value. + */ + void generateRequested(QColor accent, int intensity); + +private: + QHBoxLayout *layout; + QLabel *heading; + QLabel *accentLabel; + ColorButton *accentButton; + QLabel *intensityLabel; + QLabel *labelLow; + QLabel *labelHigh; + QSlider *intensitySlider; + QLabel *intensityPercentageLabel; + QPushButton *generateButton; +}; + +#endif // COCKATRICE_QUICK_SETUP_PANEL_H \ No newline at end of file diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp index 382fff317..d3b0252a6 100644 --- a/cockatrice/src/interface/pixel_map_generator.cpp +++ b/cockatrice/src/interface/pixel_map_generator.cpp @@ -77,8 +77,9 @@ QMap PhasePixmapGenerator::pmCache; QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name) { QString key = name + QString::number(height); - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height)); @@ -95,19 +96,22 @@ QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool hi name = "general"; } - if (highlight) + if (highlight) { name.append("_highlight"); + } QString key = name + QString::number(height); - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } QPixmap pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height)); // fall back to colorless counter if the name can't be found if (pixmap.isNull()) { name = "general"; - if (highlight) + if (highlight) { name.append("_highlight"); + } pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height)); } @@ -118,17 +122,19 @@ QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool hi QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max) { int key = size * 1000000 + max * 1000 + value; - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } QPixmap pixmap(size, size); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QColor color; - if ((max == -1) || (value == -1)) + if ((max == -1) || (value == -1)) { color = Qt::black; - else + } else { color.setHsv(120 * (1.0 - ((double)value / max)), 255, 255); + } QRadialGradient g(QPointF((double)pixmap.width() / 2, (double)pixmap.height() / 2), qMin(pixmap.width(), pixmap.height()) / 2.0); @@ -145,11 +151,13 @@ QMap PingPixmapGenerator::pmCache; QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode) { - if (countryCode.size() != 2) + if (countryCode.size() != 2) { return QPixmap(); + } QString key = countryCode + QString::number(height); - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } int width = height * 2; QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true); @@ -352,8 +360,9 @@ QPixmap LockPixmapGenerator::generatePixmap(int height) { int key = height; - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true); pmCache.insert(key, pixmap); @@ -365,8 +374,9 @@ QMap LockPixmapGenerator::pmCache; QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded) { QString key = QString::number(expanded) + ":" + QString::number(height); - if (pmCache.contains(key)) + if (pmCache.contains(key)) { return pmCache.value(key); + } QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed"; QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true); diff --git a/cockatrice/src/interface/pixel_map_generator.h b/cockatrice/src/interface/pixel_map_generator.h index 61d771c79..22f44d8db 100644 --- a/cockatrice/src/interface/pixel_map_generator.h +++ b/cockatrice/src/interface/pixel_map_generator.h @@ -1,8 +1,8 @@ /** * @file pixel_map_generator.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PIXMAPGENERATOR_H #define PIXMAPGENERATOR_H @@ -12,6 +12,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator"); diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp new file mode 100644 index 000000000..b79c91265 --- /dev/null +++ b/cockatrice/src/interface/theme_config.cpp @@ -0,0 +1,267 @@ +#include "theme_config.h" + +#include +#include +#include +#include + +bool ThemeConfig::isEmpty() const +{ + return colorScheme.isEmpty() && styleName.isEmpty(); +} + +QString ThemeConfig::toIni() const +{ + QString out; + out += "[Appearance]\n"; + out += QString("ColorScheme = %1\n").arg(colorScheme.isEmpty() ? "System" : colorScheme); + out += "\n[Style]\n"; + out += QString("Name = %1\n").arg(styleName.isEmpty() ? "Default" : styleName); + return out; +} + +ThemeConfig ThemeConfig::fromThemeDir(const QString &themeDirPath) +{ + ThemeConfig cfg; + + if (themeDirPath.isEmpty()) { + return cfg; + } + + QFile f(QDir(themeDirPath).absoluteFilePath("theme.cfg")); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return cfg; + } + + QString currentSection; + + QTextStream in(&f); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + continue; + } + + int eq = line.indexOf('='); + if (eq < 0) { + continue; + } + + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + + if (currentSection.compare("Appearance", Qt::CaseInsensitive) == 0) { + if (key.compare("ColorScheme", Qt::CaseInsensitive) == 0) { + cfg.colorScheme = value; + } + } else if (currentSection.compare("Style", Qt::CaseInsensitive) == 0) { + if (key.compare("Name", Qt::CaseInsensitive) == 0) { + cfg.styleName = value; + } + } + } + + return cfg; +} + +bool ThemeConfig::save(const QString &themeDirPath) const +{ + if (themeDirPath.isEmpty()) { + return false; + } + + QDir dir(themeDirPath); + + if (!dir.exists()) { + dir.mkpath("."); + } + + QFile f(dir.absoluteFilePath("theme.cfg")); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + + QTextStream out(&f); + out << toIni(); + + return true; +} + +bool PaletteConfig::hasPalette() const +{ + return !colors.isEmpty(); +} + +QString PaletteConfig::toToml() const +{ + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString out; + + static const QList> groups = { + {QPalette::Active, "Palette"}, + {QPalette::Disabled, "Palette.Disabled"}, + {QPalette::Inactive, "Palette.Inactive"}, + }; + + for (const auto &[group, sectionName] : groups) { + if (!colors.contains(group)) { + continue; + } + + out += QString("[%1]\n").arg(sectionName); + + const auto &roleMap = colors[group]; + + for (auto it = roleMap.cbegin(); it != roleMap.cend(); ++it) { + const char *roleName = roleEnum.valueToKey(it.key()); + + if (!roleName) { + continue; + } + + out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb)); + } + + out += "\n"; + } + + return out; +} + +QString PaletteConfig::fileName(const QString &colorScheme) +{ + return colorScheme.compare("Dark", Qt::CaseInsensitive) == 0 ? "palette-dark.toml" : "palette-light.toml"; +} + +PaletteConfig PaletteConfig::fromFile(const QString &filePath) +{ + PaletteConfig cfg; + + QFile f(filePath); + + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return cfg; + } + + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString currentSection; + QPalette::ColorGroup currentGroup = QPalette::Active; + + QTextStream in(&f); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + + if (currentSection.startsWith("Palette", Qt::CaseInsensitive)) { + int dot = currentSection.indexOf('.'); + + QString groupStr = (dot >= 0) ? currentSection.mid(dot + 1) : "Active"; + + if (groupStr.compare("Disabled", Qt::CaseInsensitive) == 0) { + currentGroup = QPalette::Disabled; + } else if (groupStr.compare("Inactive", Qt::CaseInsensitive) == 0) { + currentGroup = QPalette::Inactive; + } else { + currentGroup = QPalette::Active; + } + } + + continue; + } + + int eq = line.indexOf('='); + + if (eq < 0) { + continue; + } + + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + + // Strip inline comments if preceded by whitespace + for (int i = 1; i < value.size(); ++i) { + if (value[i] == '#' && value[i - 1].isSpace()) { + value = value.left(i).trimmed(); + break; + } + } + + if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) { + continue; + } + + if (key.startsWith("QPalette::")) { + key = key.mid(10); + } + + int roleInt = roleEnum.keyToValue(key.toUtf8().constData()); + + if (roleInt < 0) { + continue; + } + + QColor color(value); + + if (color.isValid()) { + cfg.colors[currentGroup][static_cast(roleInt)] = color; + } + } + + return cfg; +} + +PaletteConfig PaletteConfig::fromScheme(const QString &themeDirPath, const QString &colorScheme) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + + return fromFile(QDir(themeDirPath).absoluteFilePath(fileName(colorScheme))); +} + +PaletteConfig PaletteConfig::fromDefault(const QString &themeDirPath, const QString &colorScheme) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + + QDir dir(themeDirPath); + + bool wantDark = colorScheme.compare("Dark", Qt::CaseInsensitive) == 0; + + PaletteConfig cfg = + fromFile(dir.absoluteFilePath(wantDark ? "palette-default-dark.toml" : "palette-default-light.toml")); + + if (!cfg.hasPalette()) { + cfg = fromFile(dir.absoluteFilePath(wantDark ? "palette-default-light.toml" : "palette-default-dark.toml")); + } + + return cfg; +} + +QPalette PaletteConfig::apply(QPalette base) const +{ + for (auto git = colors.cbegin(); git != colors.cend(); ++git) { + for (auto rit = git.value().cbegin(); rit != git.value().cend(); ++rit) { + base.setColor(git.key(), rit.key(), rit.value()); + } + } + + return base; +} \ No newline at end of file diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h new file mode 100644 index 000000000..07bf55b7a --- /dev/null +++ b/cockatrice/src/interface/theme_config.h @@ -0,0 +1,37 @@ +#ifndef COCKATRICE_THEME_CONFIG_H +#define COCKATRICE_THEME_CONFIG_H + +#include +#include +#include +#include + +struct ThemeConfig +{ + QString colorScheme; + QString styleName; + + bool isEmpty() const; + QString toIni() const; + + static ThemeConfig fromThemeDir(const QString &themeDirPath); + bool save(const QString &themeDirPath) const; +}; + +struct PaletteConfig +{ + QMap> colors; + + bool hasPalette() const; + QString toToml() const; + + static QString fileName(const QString &colorScheme); + + static PaletteConfig fromFile(const QString &filePath); + static PaletteConfig fromScheme(const QString &themeDirPath, const QString &colorScheme); + static PaletteConfig fromDefault(const QString &themeDirPath, const QString &colorScheme); + + QPalette apply(QPalette base) const; +}; + +#endif // COCKATRICE_THEME_CONFIG_H \ No newline at end of file diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 19353b1e9..086845fe6 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -15,12 +15,11 @@ #include #include #include +#include #include #define NONE_THEME_NAME "Default" -#define FUSION_THEME_NAME "Fusion (System Default)" -#define FUSION_THEME_NAME_LIGHT "Fusion (Light)" -#define FUSION_THEME_NAME_DARK "Fusion (Dark)" +#define FUSION_THEME_NAME "Fusion" #define STYLE_CSS_NAME "style.css" #define HANDZONE_BG_NAME "handzone" #define PLAYERZONE_BG_NAME "playerzone" @@ -50,8 +49,9 @@ struct PaletteColorInfo // Iterate through all color roles (excluding NoRole and NColorRoles) for (int r = 0; r < QPalette::NColorRoles; ++r) { auto role = static_cast(r); - if (role == QPalette::NoRole) + if (role == QPalette::NoRole) { continue; + } PaletteColorInfo info; info.group = group; @@ -77,8 +77,9 @@ struct PaletteColorInfo for (int r = 0; r < QPalette::NColorRoles; ++r) { auto role = static_cast(r); - if (role == QPalette::NoRole) + if (role == QPalette::NoRole) { continue; + } QColor color = palette.color(group, role); qInfo().nospace() << qPrintable(QString("%1").arg(roleEnum.valueToKey(role), -20)) << " : " @@ -91,7 +92,7 @@ struct PaletteColorInfo ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { defaultStyleName = qApp->style()->objectName(); - // FIXME workaround for windows11 style being broken + //! \todo Workaround for windows11 style being broken. if (defaultStyleName == "windows11") { defaultStyleName = "windowsvista"; } @@ -112,20 +113,42 @@ void ThemeManager::ensureThemeDirectoryExists() } } +bool ThemeManager::isDarkMode(const QString &themeDirPath) +{ + ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath); + if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) { + return true; + } else if (themeConfig.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) { + return false; + } else { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + bool osDark = (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark); +#else + bool osDark = false; +#endif + return osDark; + } +} + +bool ThemeManager::isBuiltInTheme() +{ + const auto themeName = SettingsCache::instance().getThemeName(); + + return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME; +} + QStringMap &ThemeManager::getAvailableThemes() { QDir dir; availableThemes.clear(); - // add default value - availableThemes.insert(NONE_THEME_NAME, QString()); - // load themes from user profile dir dir.setPath(SettingsCache::instance().getThemesPath()); - availableThemes.insert(FUSION_THEME_NAME, dir.filePath("Fusion (System Default)")); - availableThemes.insert(FUSION_THEME_NAME_LIGHT, dir.filePath("Fusion (Light)")); - availableThemes.insert(FUSION_THEME_NAME_DARK, dir.filePath("Fusion (Dark)")); + // add default value + availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default")); + + availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion")); for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { if (!availableThemes.contains(themeName)) { @@ -181,202 +204,172 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) return brush; } -static inline QPalette createDarkGreenFusionPalette() +ThemeConfig ThemeManager::loadGlobalConfig(const QString &themeDirPath) { - QPalette p = QStyleFactory::create("Fusion")->standardPalette(); - - // ---------- Core backgrounds ---------- - p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e - p.setColor(QPalette::Base, QColor(45, 45, 45)); // #ff2d2d2d - p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); // #ff353535 - p.setColor(QPalette::Button, QColor(60, 60, 60)); // #ff3c3c3c - p.setColor(QPalette::ToolTipBase, QColor(60, 60, 60)); // #ff3c3c3c - - // ---------- Core text ---------- - p.setColor(QPalette::WindowText, Qt::white); // #ffffffff - p.setColor(QPalette::Text, Qt::white); // #ffffffff - p.setColor(QPalette::ButtonText, Qt::white); // #ffffffff - p.setColor(QPalette::ToolTipText, QColor(212, 212, 212)); // #ffd4d4d4 - p.setColor(QPalette::PlaceholderText, QColor(255, 255, 255, 128)); // #80ffffff - - // ---------- Selection / focus ---------- - const QColor highlight(20, 140, 60); // #ff148c3c - p.setColor(QPalette::Highlight, highlight); - p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff - - // ---------- Links ---------- - p.setColor(QPalette::Link, QColor(0, 246, 82)); // #ff00f652 - p.setColor(QPalette::LinkVisited, QColor(0, 211, 70)); // #ff00d346 - - // ---------- Accent (Qt 6) ---------- -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Accent, QColor(0, 211, 70)); // #ff00d346 -#endif - - // ---------- Bright text ---------- - p.setColor(QPalette::BrightText, QColor(0, 246, 82)); // #ff00f652 - - // ---------- 3D / frame shading ---------- - p.setColor(QPalette::Light, QColor(120, 120, 120)); // #ff787878 - p.setColor(QPalette::Midlight, QColor(90, 90, 90)); // #ff5a5a5a - p.setColor(QPalette::Mid, QColor(40, 40, 40)); // #ff282828 - p.setColor(QPalette::Dark, QColor(30, 30, 30)); // #ff1e1e1e - p.setColor(QPalette::Shadow, Qt::black); // #ff000000 - - // ---------- Disabled state ---------- - const QColor disabledText(157, 157, 157); // #ff9d9d9d - p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Text, disabledText); - p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Base, QColor(30, 30, 30)); - p.setColor(QPalette::Disabled, QPalette::Window, QColor(30, 30, 30)); - p.setColor(QPalette::Disabled, QPalette::Link, QColor(48, 140, 198)); // #ff308cc6 - p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); // #ffff00ff - p.setColor(QPalette::Disabled, QPalette::ToolTipBase, QColor(255, 255, 220)); - p.setColor(QPalette::Disabled, QPalette::ToolTipText, Qt::black); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); -#endif - - // ---------- Inactive state ---------- - p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(30, 30, 30)); - p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::white); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Inactive, QPalette::Accent, QColor(30, 30, 30)); -#endif - - return p; + return ThemeConfig::fromThemeDir(themeDirPath); } -static inline QPalette createLightGreenFusionPalette() +bool ThemeManager::saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg) { - QPalette p = QStyleFactory::create("Fusion")->standardPalette(); + return cfg.save(themeDirPath); +} - // ---------- Core backgrounds ---------- - p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0 - p.setColor(QPalette::Base, Qt::white); // #ffffffff - p.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); // #ffe9e7e3 - p.setColor(QPalette::Button, QColor(240, 240, 240)); // #fff0f0f0 - p.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); // #ffffffdc +PaletteConfig ThemeManager::loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + return PaletteConfig::fromScheme(themeDirPath, colorScheme); +} - // ---------- Core text ---------- - p.setColor(QPalette::WindowText, Qt::black); // #ff000000 - p.setColor(QPalette::Text, Qt::black); // #ff000000 - p.setColor(QPalette::ButtonText, Qt::black); // #ff000000 - p.setColor(QPalette::ToolTipText, Qt::black); // #ff000000 - p.setColor(QPalette::PlaceholderText, QColor(0, 0, 0, 128)); // #80000000 +bool ThemeManager::savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) +{ + if (themeDirPath.isEmpty()) { + return false; + } - // ---------- Selection / focus ---------- - const QColor highlight(20, 140, 60); // #ff148c3c - p.setColor(QPalette::Highlight, highlight); - p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff + QDir dir(themeDirPath); + if (!dir.exists()) { + dir.mkpath("."); + } - // ---------- Links ---------- - p.setColor(QPalette::Link, QColor(13, 95, 40)); // #ff0d5f28 - p.setColor(QPalette::LinkVisited, QColor(8, 64, 27)); // #ff08401b + QFile f(dir.absoluteFilePath(PaletteConfig::fileName(colorScheme))); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } - // ---------- Accent (Qt 6) ---------- -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Accent, QColor(16, 117, 50)); // #ff107532 + QTextStream(&f) << cfg.toToml(); + return true; +} + +void ThemeManager::setColorScheme(const QString &scheme) +{ + const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); + ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); + + cfg.colorScheme = scheme; + + cfg.save(dirPath); + reloadCurrentTheme(); +} + +void ThemeManager::reloadCurrentTheme() +{ + themeChangedSlot(); +} + +void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &scheme) +{ + const QString themeName = SettingsCache::instance().getThemeName(); + const QString dirPath = getAvailableThemes().value(themeName); + const ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath); + applyStyleAndPalette(themeName, themeCfg, cfg, scheme); +} + +void ThemeManager::applyStyleAndPalette(const QString &themeName, + const ThemeConfig &themeCfg, + const PaletteConfig &palCfg, + const QString &activeScheme) +{ + QString styleName = themeCfg.styleName; + if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) { + if (themeName == FUSION_THEME_NAME) { + styleName = "Fusion"; + } else { + styleName = defaultStyleName; + } + } + + QStyle *style = QStyleFactory::create(styleName); + if (!style) { + style = QStyleFactory::create(defaultStyleName); + } + + // Base palette + QPalette base; + if (styleName.compare("Fusion", Qt::CaseInsensitive) == 0) { + base = style->standardPalette(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + if (activeScheme == "Dark") { + base.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + } #endif + } else { + base = qApp->palette(); + } - // ---------- Bright text ---------- - p.setColor(QPalette::BrightText, Qt::white); // #ffffffff + // Overlay custom palette colours + if (palCfg.hasPalette()) { + base = palCfg.apply(base); + } - // ---------- 3D / frame shading ---------- - p.setColor(QPalette::Light, Qt::white); // #ffffffff - p.setColor(QPalette::Midlight, QColor(227, 227, 227)); // #ffe3e3e3 - p.setColor(QPalette::Mid, QColor(160, 160, 160)); // #ffa0a0a0 - p.setColor(QPalette::Dark, QColor(160, 160, 160)); // #ffa0a0a0 - p.setColor(QPalette::Shadow, QColor(105, 105, 105)); // #ff696969 + // Palette BEFORE style — setStyle() triggers a synchronous repolish of all + // widgets immediately. If the palette isn't set yet at that point, every + // widget gets polished against the stale colours, requiring a second apply + // to fully resolve. Setting palette first means setStyle's repolish cascade + // already sees the correct colours. + qApp->setPalette(base); + qApp->setStyle(style); - // ---------- Disabled state ---------- - const QColor disabledText(120, 120, 120); // #ff787878 - p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Text, disabledText); - p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Base, QColor(240, 240, 240)); - p.setColor(QPalette::Disabled, QPalette::Window, QColor(240, 240, 240)); - p.setColor(QPalette::Disabled, QPalette::Midlight, QColor(247, 247, 247)); - p.setColor(QPalette::Disabled, QPalette::AlternateBase, QColor(247, 247, 247)); - p.setColor(QPalette::Disabled, QPalette::Shadow, Qt::black); - p.setColor(QPalette::Disabled, QPalette::Link, QColor(0, 0, 255)); - p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); -#endif - - // ---------- Inactive state ---------- - p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(240, 240, 240)); - p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::black); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Inactive, QPalette::Accent, QColor(240, 240, 240)); -#endif - - return p; + // Force every widget to re-polish and repaint immediately rather than + // waiting for natural expose events, which produces a patchwork of old + // and new colours during a live preview. + // Note: we do NOT call widget->setPalette(base) here — qApp->setPalette() + // already propagates to all widgets that haven't explicitly overridden their + // palette (WA_SetPalette not set). Calling it unconditionally would clobber + // intentional per-widget palette customisations across the whole app. + for (QWidget *widget : qApp->allWidgets()) { + style->unpolish(widget); + style->polish(widget); + widget->update(); + } } void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); - qCInfo(ThemeManagerLog) << "Theme changed:" << themeName; - QString dirPath = getAvailableThemes().value(themeName); - QDir dir = dirPath; + currentThemePath = dirPath; + QDir dir(dirPath); - // css + // CSS if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) { qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME)); } else { qApp->setStyleSheet(""); } - if (themeName == FUSION_THEME_NAME) { - QStyle *fusionStyle = QStyleFactory::create("Fusion"); - qApp->setStyle(fusionStyle); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) - // Start from Fusion's own palette so dark mode is handled correctly, - // then apply any tweaks on top of it. - QPalette palette = fusionStyle->standardPalette(); - if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { - palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); - } - qApp->setPalette(palette); -#endif - } else if (themeName == FUSION_THEME_NAME_LIGHT) { - qApp->setStyle(QStyleFactory::create("Fusion")); - qApp->setPalette(createLightGreenFusionPalette()); - } else if (themeName == FUSION_THEME_NAME_DARK) { - qApp->setStyle(QStyleFactory::create("Fusion")); - qApp->setPalette(createDarkGreenFusionPalette()); - } else { - qApp->setStyle(QStyleFactory::create(defaultStyleName)); // setting the style also sets the palette + // load theme.cfg for style + scheme preference + ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath); + + // Resolve active scheme: + // theme.cfg says Dark/Light → use that + // theme.cfg says System or is absent → follow the OS + QString activeScheme = isDarkMode(dirPath) ? "Dark" : "Light"; + + // ── Load palette: custom first, then theme default ──────────────────── + PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme); + if (!palette.hasPalette()) { + palette = PaletteConfig::fromDefault(dirPath, activeScheme); } - if (dirPath.isEmpty()) { - // set default values - QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS); - brushes[Role::Hand] = HANDZONE_BG_DEFAULT; - brushes[Role::Table] = TABLEZONE_BG_DEFAULT; - brushes[Role::Player] = PLAYERZONE_BG_DEFAULT; - brushes[Role::Stack] = STACKZONE_BG_DEFAULT; - } else { - // resources - QStringList resources; - resources << dir.absolutePath() << DEFAULT_RESOURCE_PATHS; - QDir::setSearchPaths("theme", resources); + applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); - // zones bg - dir.cd("zones"); - brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT); - brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT); - brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT); - brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT); + QStringList resources; + if (!dirPath.isEmpty()) { + resources << dir.absolutePath(); } + resources << DEFAULT_RESOURCE_PATHS; + + QDir::setSearchPaths("theme", resources); + + brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT); + + brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT); + + brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT); + + brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT); for (auto &brushCache : brushesCache) { brushCache.clear(); } diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index d942f0fef..b9e764d08 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -1,12 +1,14 @@ /** * @file theme_manager.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef THEMEMANAGER_H #define THEMEMANAGER_H +#include "theme_config.h" + #include #include #include @@ -41,6 +43,7 @@ public: private: QString defaultStyleName; + QString currentThemePath; std::array brushes; QStringMap availableThemes; /* @@ -52,9 +55,31 @@ protected: void ensureThemeDirectoryExists(); QBrush loadBrush(QString fileName, QColor fallbackColor); QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush); + void applyStyleAndPalette(const QString &themeName, + const ThemeConfig &themeCfg, + const PaletteConfig &palCfg, + const QString &activeScheme); public: + bool isBuiltInTheme(); + bool isDarkMode(const QString &themeDirPath); QStringMap &getAvailableThemes(); + // Returns the path to the currently active theme directory (empty = default) + QString getCurrentThemePath() const + { + return currentThemePath; + } + // Load the global theme settings (style + color scheme preference) + static ThemeConfig loadGlobalConfig(const QString &themeDirPath); + static bool saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg); + + // Load/save per-scheme palette colors + static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); + static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); + void setColorScheme(const QString &scheme); + + void reloadCurrentTheme(); + void previewPalette(const PaletteConfig &cfg, const QString &scheme); QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h index 6512ba890..f776d4c77 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h @@ -1,8 +1,8 @@ /** * @file color_identity_widget.h * @ingroup CardExtraInfoWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COLOR_IDENTITY_WIDGET_H #define COLOR_IDENTITY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h index 26a186c59..b2f6b62c0 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h @@ -1,8 +1,8 @@ /** * @file mana_cost_widget.h * @ingroup CardExtraInfoWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MANA_COST_WIDGET_H #define MANA_COST_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h index 8d2c6813f..0f2d7acd1 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h @@ -1,8 +1,8 @@ /** * @file mana_symbol_widget.h * @ingroup CardExtraInfoWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MANA_SYMBOL_WIDGET_H #define MANA_SYMBOL_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index fa304816f..3f36e559c 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -58,16 +58,6 @@ void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) } } -void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) -{ - emit cardClicked(event, card); -} - -void CardGroupDisplayWidget::onHover(const ExactCard &card) -{ - emit cardHovered(card); -} - void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { auto proxyModel = qobject_cast(selectionModel->model()); @@ -95,8 +85,9 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, for (auto &range : deselected) { for (int row = range.top(); row <= range.bottom(); ++row) { QModelIndex idx = range.model()->index(row, 0, range.parent()); - if (proxyModel) + if (proxyModel) { idx = proxyModel->mapToSource(idx); + } auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); if (it != indexToWidgetMap.end()) { @@ -153,8 +144,8 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i widget->setScaleFactor(cardSizeWidget->getSlider()->value()); widget->setCard(CardDatabaseManager::query()->getCard({cardName, cardProviderId})); - connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &CardGroupDisplayWidget::onClick); - connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::onHover); + connect(widget, &CardInfoPictureWithTextOverlayWidget::cardClicked, this, &CardGroupDisplayWidget::cardClicked); + connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::cardHovered); connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor); indexToWidgetMap[index].append(widget); diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h index 0f1209b29..2308ccf8d 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -1,8 +1,8 @@ /** * @file card_group_display_widget.h * @ingroup DeckEditorCardGroupWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_GROUP_DISPLAY_WIDGET_H #define CARD_GROUP_DISPLAY_WIDGET_H @@ -48,8 +48,6 @@ public: public slots: void mousePressEvent(QMouseEvent *event) override; - void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); - void onHover(const ExactCard &card); virtual QWidget *constructWidgetForIndex(QPersistentModelIndex index); virtual void updateCardDisplays(); virtual void onCardAddition(const QModelIndex &parent, int first, int last); @@ -59,7 +57,7 @@ public slots: void resizeEvent(QResizeEvent *event) override; signals: - void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); + void cardClicked(QMouseEvent *event, const ExactCard &card); void cardHovered(const ExactCard &card); void cleanupRequested(CardGroupDisplayWidget *cardGroupDisplayWidget); diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h index e07152b34..0772c1ab4 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h @@ -1,8 +1,8 @@ /** * @file flat_card_group_display_widget.h * @ingroup DeckEditorCardGroupWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H #define FLAT_CARD_GROUP_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h index 288e46129..5b4b80d6b 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h @@ -1,8 +1,8 @@ /** * @file overlapped_card_group_display_widget.h * @ingroup DeckEditorCardGroupWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H #define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index 4930fbbfd..577dafe0a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -1,6 +1,6 @@ #include "card_info_display_widget.h" -#include "../../../game/board/card_item.h" +#include "../../../game_graphics/board/card_item.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" @@ -43,11 +43,13 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa void CardInfoDisplayWidget::setCard(const ExactCard &card) { - if (exactCard) + if (exactCard) { disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr); + } exactCard = card; - if (exactCard) + if (exactCard) { connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear); + } text->setCard(exactCard); pic->setCard(exactCard); diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.h b/cockatrice/src/interface/widgets/cards/card_info_display_widget.h index d44c4c205..f8f33f3d4 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.h @@ -1,8 +1,8 @@ /** * @file card_info_display_widget.h * @ingroup CardWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDINFOWIDGET_H #define CARDINFOWIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp index 21bee8f54..2e7c62461 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp @@ -1,7 +1,7 @@ #include "card_info_frame_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../../game/board/card_item.h" +#include "../../../game_graphics/board/card_item.h" #include "card_info_display_widget.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" diff --git a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.h b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.h index 11b991e87..e766fac7a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.h @@ -1,8 +1,8 @@ /** * @file card_info_frame_widget.h * @ingroup CardWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDFRAME_H #define CARDFRAME_H diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h index 0185f758c..ce22a1a44 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h @@ -1,8 +1,8 @@ /** * @file card_info_picture_art_crop_widget.h * @ingroup CardWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_INFO_PICTURE_ART_CROP_WIDGET_H #define CARD_INFO_PICTURE_ART_CROP_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h index 10eb32940..a158ac6cf 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h @@ -2,8 +2,8 @@ * @file card_info_picture_enlarged_widget.h * @ingroup CardWigets * @ingroup DeckEditorWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_PICTURE_ENLARGED_WIDGET_H #define CARD_PICTURE_ENLARGED_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp index 816940b0f..3bfd9ce7d 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -1,7 +1,7 @@ #include "card_info_picture_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../../game/board/card_item.h" +#include "../../../game_graphics/board/card_item.h" #include "../../../interface/card_picture_loader/card_picture_loader.h" #include "../../../interface/widgets/tabs/tab_supervisor.h" #include "../../window_main.h" @@ -343,11 +343,9 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) QWidget::mousePressEvent(event); if (event->button() == Qt::RightButton) { createRightClickMenu()->popup(QCursor::pos()); - } else { - emit cardClicked(); } - emit cardClicked(); + emit cardClicked(event, exactCard); } void CardInfoPictureWidget::hideEvent(QHideEvent *event) @@ -433,13 +431,13 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu() QAction *addCard = addCardMenu->addAction(tr("Mainboard")); connect(addCard, &QAction::triggered, this, [this, deckEditorTab] { deckEditorTab->updateCard(exactCard); - deckEditorTab->actAddCard(exactCard); + deckEditorTab->addCard(exactCard, DECK_ZONE_MAIN); }); QAction *addCardSideboard = addCardMenu->addAction(tr("Sideboard")); connect(addCardSideboard, &QAction::triggered, this, [this, deckEditorTab] { deckEditorTab->updateCard(exactCard); - deckEditorTab->actAddCardToSideboard(exactCard); + deckEditorTab->addCard(exactCard, DECK_ZONE_SIDE); }); } diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h index 1095d7d74..1f065eed9 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -1,8 +1,8 @@ /** * @file card_info_picture_widget.h * @ingroup CardWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_INFO_PICTURE_H #define CARD_INFO_PICTURE_H @@ -43,7 +43,7 @@ signals: void hoveredOnCard(const ExactCard &hoveredCard); void cardScaleFactorChanged(int _scale); void cardChanged(const ExactCard &card); - void cardClicked(); + void cardClicked(QMouseEvent *event, const ExactCard &card); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 2f0aeccfd..c5cb59b3b 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -93,7 +93,7 @@ void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted) void CardInfoPictureWithTextOverlayWidget::mousePressEvent(QMouseEvent *event) { - emit imageClicked(event, this); + emit cardClicked(event, getCard()); } /** diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index eed827292..ba978498d 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -2,8 +2,8 @@ * @file card_info_picture_with_text_overlay_widget.h * @ingroup CardWidgets * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_PICTURE_WITH_TEXT_OVERLAY_H #define CARD_PICTURE_WITH_TEXT_OVERLAY_H @@ -35,8 +35,6 @@ public: void setHighlighted(bool _highlighted); [[nodiscard]] QSize sizeHint() const override; -signals: - void imageClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); protected: void paintEvent(QPaintEvent *event) override; diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp index 456e1533a..c6af5320b 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -1,6 +1,6 @@ #include "card_info_text_widget.h" -#include "../../../game/board/card_item.h" +#include "../../../game_graphics/board/card_item.h" #include #include @@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard) text += QString("%1%2") .arg(tr("Name:"), card->getName().toHtmlEscaped()); - if (exactCard.getPrinting() != PrintingInfo()) { + if (!exactCard.getPrinting().isEmpty()) { QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped(); QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped(); @@ -73,8 +73,9 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard) QStringList cardProps = card->getProperties(); for (const QString &key : cardProps) { - if (key.contains("-")) + if (key.contains("-")) { continue; + } QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":"; text += QString("%1%2").arg(keyText, card->getProperty(key).toHtmlEscaped()); diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h index 95b588882..a9c29da37 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h @@ -1,8 +1,8 @@ /** * @file card_info_text_widget.h * @ingroup CardWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDINFOTEXT_H #define CARDINFOTEXT_H diff --git a/cockatrice/src/interface/widgets/cards/card_size_widget.h b/cockatrice/src/interface/widgets/cards/card_size_widget.h index 638c263ea..9f4c165fd 100644 --- a/cockatrice/src/interface/widgets/cards/card_size_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_size_widget.h @@ -3,8 +3,8 @@ * @ingroup CardWidgets * @ingroup DeckEditorWidgets * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_SIZE_WIDGET_H #define CARD_SIZE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index a8a97a4ca..eaf3a67b0 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -51,7 +51,7 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, // User Interaction // ===================================================================================================================== -void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) +void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, const ExactCard &card) { emit cardClicked(event, card, zoneName); } diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h index dc0f3d734..b426fca30 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h @@ -1,8 +1,8 @@ /** * @file deck_card_zone_display_widget.h * @ingroup DeckEditorWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H #define DECK_CARD_ZONE_DISPLAY_WIDGET_H @@ -42,7 +42,7 @@ public: void addCardsToOverlapWidget(); public slots: - void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); + void onClick(QMouseEvent *event, const ExactCard &card); void onHover(const ExactCard &card); void cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget); void constructAppropriateWidget(QPersistentModelIndex index); @@ -55,7 +55,7 @@ public slots: void onCategoryRemoval(const QModelIndex &parent, int first, int last); signals: - void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName); + void cardClicked(QMouseEvent *event, const ExactCard &card, const QString &zoneName); void cardHovered(const ExactCard &card); void activeSortCriteriaChanged(QStringList activeSortCriteria); void requestCleanup(DeckCardZoneDisplayWidget *displayWidget); diff --git a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h index acb67a49a..154e938aa 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h @@ -2,8 +2,8 @@ * @file deck_preview_card_picture_widget.h * @ingroup CardWidgets * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H #define DECK_PREVIEW_CARD_PICTURE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp index 7af641689..e807e9d47 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp @@ -17,8 +17,9 @@ AbstractAnalyticsPanelWidget * AnalyticsPanelWidgetFactory::create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const { auto it = widgets.find(type); - if (it == widgets.end()) + if (it == widgets.end()) { return nullptr; + } auto w = it->creator(parent, analyzer); diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp index 4931aeaa4..0107294c7 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp @@ -20,12 +20,11 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer) : AbstractAnalyticsPanelWidget(parent, analyzer) { - controls = new QWidget(this); - controlLayout = new QHBoxLayout(controls); - controlLayout->setContentsMargins(11, 0, 11, 0); + controls = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + controls->setSpacing(4, 4); labelPrefix = new QLabel(this); - controlLayout->addWidget(labelPrefix); + controls->addWidget(labelPrefix); criteriaCombo = new QComboBox(this); // Give these things item-data so we can translate the actual user-facing strings @@ -33,33 +32,32 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics criteriaCombo->addItem(QString(), "type"); criteriaCombo->addItem(QString(), "subtype"); criteriaCombo->addItem(QString(), "cmc"); - controlLayout->addWidget(criteriaCombo); + controls->addWidget(criteriaCombo); exactnessCombo = new QComboBox(this); exactnessCombo->addItem(QString(), true); // At least exactnessCombo->addItem(QString(), false); // Exactly - controlLayout->addWidget(exactnessCombo); + controls->addWidget(exactnessCombo); quantitySpin = new QSpinBox(this); quantitySpin->setRange(1, 60); - controlLayout->addWidget(quantitySpin); + controls->addWidget(quantitySpin); labelMiddle = new QLabel(this); - controlLayout->addWidget(labelMiddle); + controls->addWidget(labelMiddle); drawnSpin = new QSpinBox(this); drawnSpin->setRange(1, 60); drawnSpin->setValue(7); - controlLayout->addWidget(drawnSpin); + controls->addWidget(drawnSpin); labelSuffix = new QLabel(this); - controlLayout->addWidget(labelSuffix); + controls->addWidget(labelSuffix); labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - controlLayout->addStretch(1); layout->addWidget(controls); // Table diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h index 80015999f..9f7b971b1 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h @@ -1,6 +1,8 @@ #ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H #define COCKATRICE_DRAW_PROBABILITY_WIDGET_H +#include "../../../../layouts/flow_layout.h" +#include "../../../general/layout_containers/flow_widget.h" #include "../../abstract_analytics_panel_widget.h" #include "../../deck_list_statistics_analyzer.h" #include "draw_probability_config.h" @@ -31,8 +33,7 @@ private slots: private: DrawProbabilityConfig config; - QWidget *controls; - QHBoxLayout *controlLayout; + FlowWidget *controls; QLabel *labelPrefix; QLabel *labelMiddle; QLabel *labelSuffix; diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp index 3317486ea..83e35d57d 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp @@ -28,8 +28,9 @@ ManaBaseConfigDialog::ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer, // select initial filters for (int i = 0; i < filterList->count(); ++i) { - if (config.filters.contains(filterList->item(i)->text())) + if (config.filters.contains(filterList->item(i)->text())) { filterList->item(i)->setSelected(true); + } } buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h index 39380c07e..85b2a13b3 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h @@ -1,8 +1,8 @@ /** * @file mana_base_widget.h * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MANA_BASE_WIDGET_H #define MANA_BASE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp index 38199656c..9e9ae98a6 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp @@ -69,8 +69,9 @@ void ManaCurveConfigDialog::setFromConfig(const ManaCurveConfig &cfg) { groupBy->setCurrentText(cfg.groupBy); // restore filters - for (int i = 0; i < filterList->count(); ++i) + for (int i = 0; i < filterList->count(); ++i) { filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text())); + } showMain->setChecked(cfg.showMain); showCatRows->setChecked(cfg.showCategoryRows); @@ -81,8 +82,9 @@ void ManaCurveConfigDialog::accept() cfg.groupBy = groupBy->currentText(); cfg.filters.clear(); - for (auto *item : filterList->selectedItems()) + for (auto *item : filterList->selectedItems()) { cfg.filters << item->text(); + } cfg.showMain = showMain->isChecked(); cfg.showCategoryRows = showCatRows->isChecked(); diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp index f059ff873..c04767015 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp @@ -51,15 +51,17 @@ void ManaCurveTotalWidget::updateDisplay(const QString &categoryName, for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) { const QString &category = it.key(); - if (!config.filters.isEmpty() && !config.filters.contains(category)) + if (!config.filters.isEmpty() && !config.filters.contains(category)) { continue; + } const int value = it.value(); QStringList cards; const auto catIt = cardsMap.constFind(category); - if (catIt != cardsMap.cend()) + if (catIt != cardsMap.cend()) { cards = catIt->value(cmc); + } segments.push_back({category, value, cards, GameSpecificColors::MTG::colorHelper(category)}); } diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp index e09ecfe87..b182bf954 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp @@ -69,16 +69,18 @@ static void buildMapsByCategory(const QHash> &categoryC const QString &category = catIt.key(); const auto &countsByCmc = catIt.value(); - for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) + for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) { outCmcMap[it.key()][category] = it.value(); + } } for (auto catIt = categoryCards.cbegin(); catIt != categoryCards.cend(); ++catIt) { const QString &category = catIt.key(); const auto &cardsByCmc = catIt.value(); - for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it) + for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it) { outCardsMap[category][it.key()] = it.value(); + } } } @@ -88,8 +90,9 @@ static void findGlobalCmcRange(const QHash> &categoryCo maxCmc = 0; for (const auto &countsByCmc : categoryCounts) { - for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) + for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) { maxCmc = qMax(maxCmc, it.key()); + } } } diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h index da59da9a8..d3e56d44c 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h @@ -1,8 +1,8 @@ /** * @file mana_curve_widget.h * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MANA_CURVE_WIDGET_H #define MANA_CURVE_WIDGET_H @@ -37,7 +37,7 @@ public: { config = ManaCurveConfig::fromJson(o); updateDisplay(); - }; + } QJsonObject extractConfigFromDialog(QDialog *dlg) const override; diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp index 80fd03928..b01c33ba5 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp @@ -26,8 +26,9 @@ ManaDevotionConfigDialog::ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *a // select initial filters for (int i = 0; i < filterList->count(); ++i) { - if (config.filters.contains(filterList->item(i)->text())) + if (config.filters.contains(filterList->item(i)->text())) { filterList->item(i)->setSelected(true); + } } buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h index 833f12938..af0215305 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h @@ -1,8 +1,8 @@ /** * @file mana_devotion_widget.h * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MANA_DEVOTION_WIDGET_H #define MANA_DEVOTION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp index f70f32d5b..01af9329c 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp @@ -24,8 +24,9 @@ ManaDistributionConfig ManaDistributionConfig::fromJson(const QJsonObject &o) if (o.contains("filters")) { config.filters.clear(); - for (auto v : o["filters"].toArray()) + for (auto v : o["filters"].toArray()) { config.filters << v.toString(); + } } if (o.contains("showColorRows")) { diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp index 7fe4d94e4..325c70028 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp @@ -62,8 +62,9 @@ void ManaDistributionConfigDialog::setFromConfig(const ManaDistributionConfig &c displayType->setCurrentText(cfg.displayType); - for (int i = 0; i < filterList->count(); ++i) + for (int i = 0; i < filterList->count(); ++i) { filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text())); + } showColorRows->setChecked(cfg.showColorRows); } @@ -74,8 +75,9 @@ void ManaDistributionConfigDialog::accept() // Filters cfg.filters.clear(); - for (auto *item : filterList->selectedItems()) + for (auto *item : filterList->selectedItems()) { cfg.filters << item->text(); + } cfg.showColorRows = showColorRows->isChecked(); diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp index ea61302f0..76552ea2f 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp @@ -23,16 +23,20 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal layout = new QVBoxLayout(this); // Controls - controlContainer = new QWidget(this); - controlLayout = new QHBoxLayout(controlContainer); + controlContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + controlContainer->setSpacing(4, 4); addButton = new QPushButton(this); removeButton = new QPushButton(this); saveButton = new QPushButton(this); loadButton = new QPushButton(this); - controlLayout->addWidget(addButton); - controlLayout->addWidget(removeButton); - controlLayout->addWidget(saveButton); - controlLayout->addWidget(loadButton); + includeSideboardCheckBox = new QCheckBox(this); + includeSideboardCheckBox->setChecked(false); + + controlContainer->addWidget(addButton); + controlContainer->addWidget(removeButton); + controlContainer->addWidget(saveButton); + controlContainer->addWidget(loadButton); + controlContainer->addWidget(includeSideboardCheckBox); layout->addWidget(controlContainer); @@ -40,6 +44,7 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected); connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout); connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout); + connect(includeSideboardCheckBox, &QCheckBox::clicked, this, &DeckAnalyticsWidget::includeSideboardChanged); // Scroll area and container scrollArea = new QScrollArea(this); @@ -66,6 +71,13 @@ void DeckAnalyticsWidget::retranslateUi() removeButton->setText(tr("Remove Panel")); saveButton->setText(tr("Save Layout")); loadButton->setText(tr("Load Layout")); + includeSideboardCheckBox->setText(tr("Include Sideboard")); +} + +void DeckAnalyticsWidget::includeSideboardChanged(bool checked) +{ + statsAnalyzer->getConfig().includeSideboard = checked; + updateDisplays(); } void DeckAnalyticsWidget::updateDisplays() diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index 31ee36fbb..3c73deca2 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -7,10 +7,12 @@ #ifndef DECK_ANALYTICS_WIDGET_H #define DECK_ANALYTICS_WIDGET_H +#include "../general/layout_containers/flow_widget.h" #include "abstract_analytics_panel_widget.h" #include "deck_list_statistics_analyzer.h" #include "resizable_panel.h" +#include #include #include #include @@ -29,6 +31,7 @@ public slots: public: explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); void retranslateUi(); + void includeSideboardChanged(bool checked); private slots: void onAddPanel(); @@ -49,14 +52,15 @@ private: void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {}); QVBoxLayout *layout; - QWidget *controlContainer; - QHBoxLayout *controlLayout; + FlowWidget *controlContainer; QPushButton *addButton; QPushButton *removeButton; QPushButton *saveButton; QPushButton *loadButton; + QCheckBox *includeSideboardCheckBox; + QScrollArea *scrollArea; QWidget *panelContainer; QVBoxLayout *panelLayout; diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index ad8afb766..add13ff21 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -19,7 +19,13 @@ void DeckListStatisticsAnalyzer::analyze() { clearData(); - QList nodes = model->getCardNodes(); + QList nodes; + + if (config.includeSideboard) { + nodes = model->getCardNodes(); + } else { + nodes = model->getCardNodesForZone(DECK_ZONE_MAIN); + } for (auto node : nodes) { CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); @@ -185,10 +191,12 @@ double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k) } auto choose = [](int n, int r) -> double { - if (r > n) + if (r > n) { return 0.0; - if (r == 0 || r == n) + } + if (r == 0 || r == n) { return 1.0; + } double res = 1.0; for (int i = 1; i <= r; ++i) { res *= (n - r + i); diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h index 946bb0117..52ae751bf 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h @@ -17,6 +17,7 @@ struct DeckListStatisticsAnalyzerConfig bool computeCategories = true; bool computeCurveBreakdowns = true; bool computeProbabilities = true; + bool includeSideboard = false; }; class DeckListStatisticsAnalyzer : public QObject @@ -133,6 +134,11 @@ public: return model; } + DeckListStatisticsAnalyzerConfig &getConfig() + { + return config; + } + signals: void statsUpdated(); diff --git a/cockatrice/src/interface/widgets/deck_editor/card_database_view.cpp b/cockatrice/src/interface/widgets/deck_editor/card_database_view.cpp new file mode 100644 index 000000000..a1c29e241 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/card_database_view.cpp @@ -0,0 +1,167 @@ +#include "card_database_view.h" + +#include "../../../client/settings/cache_settings.h" +#include "card_database_display_model.h" +#include "card_database_model.h" + +#include +#include +#include +#include +#include +#include +#include + +static bool canBeCommander(const CardInfo &cardInfo) +{ + return (cardInfo.getCardType().contains("Legendary", Qt::CaseInsensitive) && + cardInfo.getCardType().contains("Creature", Qt::CaseInsensitive)) || + cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive); +} + +CardDatabaseView::CardDatabaseView(QWidget *parent, CardDatabaseDisplayModel *model) + : QTreeView(parent), databaseDisplayModel(model) +{ + // set up object + setUniformRowHeights(true); + setRootIsDecorated(false); + setAlternatingRowColors(true); + setSortingEnabled(true); + sortByColumn(0, Qt::AscendingOrder); + QTreeView::setModel(databaseDisplayModel); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, + &CardDatabaseView::resetSelectionIfEmpty); + + connect(this, &QTreeView::customContextMenuRequested, this, &CardDatabaseView::openCustomMenu); + connect(selectionModel(), &QItemSelectionModel::currentRowChanged, this, &CardDatabaseView::updateCard); + connect(this, &QTreeView::doubleClicked, this, &CardDatabaseView::actDoubleClick); + + // layout settings + QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState(); + if (dbHeaderState.isNull()) { + // first run + setColumnWidth(0, 200); + } else { + header()->restoreState(dbHeaderState); + } + connect(header(), &QHeaderView::geometriesChanged, this, &CardDatabaseView::saveDbHeaderState); + + // create key filters + searchKeySignals.setObjectName("searchKeySignals"); + connect(&searchKeySignals, &KeySignals::onEnter, this, [this] { addCard(DECK_ZONE_MAIN); }); + connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, this, [this] { addCard(DECK_ZONE_MAIN); }); + connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, this, [this] { addCard(DECK_ZONE_SIDE); }); + connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, this, [this] { decrementCard(DECK_ZONE_MAIN); }); + connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, this, [this] { decrementCard(DECK_ZONE_SIDE); }); + connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, this, [this] { addCard(DECK_ZONE_SIDE); }); + connect(&searchKeySignals, &KeySignals::onCtrlEnter, this, [this] { addCard(DECK_ZONE_SIDE); }); + connect(&searchKeySignals, &KeySignals::onCtrlC, this, &CardDatabaseView::copyDatabaseCellContents); +} + +QString CardDatabaseView::currentCardName() const +{ + const QModelIndex currentIndex = selectionModel()->currentIndex(); + if (!currentIndex.isValid()) { + return {}; + } + + return currentIndex.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); +} + +void CardDatabaseView::actDoubleClick() +{ + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + addCard(DECK_ZONE_SIDE); + } else { + addCard(DECK_ZONE_MAIN); + } +} + +void CardDatabaseView::addCard(const QString &zoneName) +{ + emit cardAdded(currentCardName(), zoneName); +} + +void CardDatabaseView::decrementCard(const QString &zoneName) +{ + emit cardDecremented(currentCardName(), zoneName); +} + +void CardDatabaseView::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + if (!current.isValid()) { + return; + } + + const QString cardName = current.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); + + if (!current.model()->hasChildren(current.siblingAtColumn(CardDatabaseModel::NameColumn))) { + emit cardChanged(cardName); + } +} + +void CardDatabaseView::resetSelectionIfEmpty() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + if (sel.isEmpty() && databaseDisplayModel->rowCount() > 0) { + selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } +} + +void CardDatabaseView::copyDatabaseCellContents() const +{ + auto _data = selectionModel()->currentIndex().data(); + QApplication::clipboard()->setText(_data.toString()); +} + +void CardDatabaseView::saveDbHeaderState() +{ + SettingsCache::instance().layouts().setDeckEditorDbHeaderState(header()->saveState()); +} + +void CardDatabaseView::openCustomMenu(QPoint point) +{ + CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(currentCardName()); + + if (!card) { + return; + } + + QMenu menu; + // add to deck and sideboard options + QAction *addToDeck = menu.addAction(tr("Add to Deck")); + QAction *addToSideboard = menu.addAction(tr("Add to Sideboard")); + QAction *selectPrinting = menu.addAction(tr("Select Printing")); + + connect(addToDeck, &QAction::triggered, this, [this, card] { emit cardAdded(card->getName(), DECK_ZONE_MAIN); }); + connect(addToSideboard, &QAction::triggered, this, + [this, card] { emit cardAdded(card->getName(), DECK_ZONE_SIDE); }); + connect(selectPrinting, &QAction::triggered, this, &CardDatabaseView::selectPrintingClicked); + + if (canBeCommander(*card)) { + QAction *edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)")); + connect(edhRecCommander, &QAction::triggered, this, [this, card] { emit edhrecClicked(card, true); }); + } + QAction *edhRecCard = menu.addAction(tr("Show on EDHRec (Card)")); + connect(edhRecCard, &QAction::triggered, this, [this, card] { emit edhrecClicked(card, false); }); + + // filling out the related cards submenu + auto *relatedMenu = new QMenu(tr("Show Related cards")); + menu.addMenu(relatedMenu); + auto relatedCards = card->getAllRelatedCards(); + if (relatedCards.isEmpty()) { + relatedMenu->setDisabled(true); + } else { + for (const CardRelation *rel : relatedCards) { + const QString &relatedCardName = rel->getName(); + QAction *relatedCard = relatedMenu->addAction(relatedCardName); + connect(relatedCard, &QAction::triggered, this, + [this, relatedCardName] { emit relatedCardClicked(relatedCardName); }); + } + } + + menu.exec(mapToGlobal(point)); +} diff --git a/cockatrice/src/interface/widgets/deck_editor/card_database_view.h b/cockatrice/src/interface/widgets/deck_editor/card_database_view.h new file mode 100644 index 000000000..175ec12b9 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/card_database_view.h @@ -0,0 +1,59 @@ +#ifndef COCKATRICE_CARD_DATABASE_VIEW_H +#define COCKATRICE_CARD_DATABASE_VIEW_H + +#include "../../key_signals.h" + +#include +#include + +class CardDatabaseModel; +class CardDatabaseDisplayModel; + +/** + * @brief The card database table. + */ +class CardDatabaseView : public QTreeView +{ + Q_OBJECT + + KeySignals searchKeySignals; + CardDatabaseDisplayModel *databaseDisplayModel; + +public: + explicit CardDatabaseView(QWidget *parent, CardDatabaseDisplayModel *model); + + QString currentCardName() const; + + /** + * @brief Get the KeySignals that are connected to this view. + * You can install the KeySignals as an eventFilter to capture keyboard shortcuts for adding and decrementing cards. + */ + KeySignals *getKeySignals() + { + return &searchKeySignals; + } + +signals: + void cardChanged(const QString &cardName); + + void cardAdded(const QString &cardName, const QString &zoneName); + void cardDecremented(const QString &cardName, const QString &zoneName); + + void edhrecClicked(const CardInfoPtr &cardInfo, bool isCommander); + void selectPrintingClicked(); + void relatedCardClicked(const QString &relatedCard); + +private slots: + void actDoubleClick(); + + void addCard(const QString &zoneName); + void decrementCard(const QString &zoneName); + void updateCard(const QModelIndex ¤t, const QModelIndex &); + + void resetSelectionIfEmpty(); + void copyDatabaseCellContents() const; + void saveDbHeaderState(); + void openCustomMenu(QPoint point); +}; + +#endif // COCKATRICE_CARD_DATABASE_VIEW_H diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp index bacebe385..2a491de4f 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp @@ -13,7 +13,7 @@ DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDe void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor) { - databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor); + databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor->databaseModel); auto *frame = new QVBoxLayout; frame->setObjectName("databaseDisplayFrame"); @@ -29,19 +29,16 @@ void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeck // connect signals connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, deckEditor, &AbstractTabDeckEditor::updateCard); - connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, deckEditor, - &AbstractTabDeckEditor::actAddCard); - connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, deckEditor, - &AbstractTabDeckEditor::actAddCardToSideboard); - connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, deckEditor, - &AbstractTabDeckEditor::actDecrementCard); - connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, deckEditor, - &AbstractTabDeckEditor::actDecrementCardFromSideboard); -} - -CardDatabase *DeckEditorCardDatabaseDockWidget::getDatabase() const -{ - return databaseDisplayWidget->databaseModel->getDatabase(); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardAdded, deckEditor, + &AbstractTabDeckEditor::addCard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardDecremented, deckEditor, + &AbstractTabDeckEditor::decrementCard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::edhrecRequested, deckEditor, + &AbstractTabDeckEditor::openEdhrecTab); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::printingSelectorRequested, deckEditor, + &AbstractTabDeckEditor::showPrintingSelector); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardInfoRequested, deckEditor, + &AbstractTabDeckEditor::updateCardInfo); } void DeckEditorCardDatabaseDockWidget::retranslateUi() @@ -58,7 +55,3 @@ void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters() { databaseDisplayWidget->clearAllDatabaseFilters(); } -void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit() -{ - databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length()); -} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h index 6ad442075..6af2e4432 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h @@ -17,13 +17,11 @@ public: DeckEditorDatabaseDisplayWidget *databaseDisplayWidget; - CardDatabase *getDatabase() const; void setFilterTree(FilterTree *filterTree); public slots: void retranslateUi(); void clearAllDatabaseFilters(); - void highlightAllSearchEdit(); private: void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp index c625ff1d9..9da821813 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -5,24 +5,17 @@ #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" #include "../../../interface/widgets/tabs/tab_supervisor.h" #include "../../pixel_map_generator.h" +#include "card_database_view.h" #include #include -#include #include #include #include #include -static bool canBeCommander(const CardInfo &cardInfo) -{ - return (cardInfo.getCardType().contains("Legendary", Qt::CaseInsensitive) && - cardInfo.getCardType().contains("Creature", Qt::CaseInsensitive)) || - cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive); -} - -DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor) - : QWidget(parent), deckEditor(deckEditor) +DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, CardDatabaseModel *databaseModel) + : QWidget(parent) { setObjectName("databaseDisplayWidget"); @@ -36,62 +29,34 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent searchEdit->setClearButtonEnabled(true); searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); - searchEdit->installEventFilter(&searchKeySignals); setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); - searchKeySignals.setObjectName("searchKeySignals"); - connect(searchEdit, &SearchLineEdit::textChanged, this, &DeckEditorDatabaseDisplayWidget::updateSearch); - connect(&searchKeySignals, &KeySignals::onEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, this, - &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, this, - &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, this, - &DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, this, - &DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, this, - &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlC, this, &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents); connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); - databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); - databaseModel->setObjectName("databaseModel"); databaseDisplayModel = new CardDatabaseDisplayModel(this); databaseDisplayModel->setObjectName("databaseDisplayModel"); databaseDisplayModel->setSourceModel(databaseModel); databaseDisplayModel->setFilterKeyColumn(0); - databaseView = new QTreeView(this); + databaseView = new CardDatabaseView(this, databaseDisplayModel); databaseView->setObjectName("databaseView"); databaseView->setFocusProxy(searchEdit); - databaseView->setUniformRowHeights(true); - databaseView->setRootIsDecorated(false); - databaseView->setAlternatingRowColors(true); - databaseView->setSortingEnabled(true); - databaseView->sortByColumn(0, Qt::AscendingOrder); - databaseView->setModel(databaseDisplayModel); - databaseView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(databaseView, &QTreeView::customContextMenuRequested, this, - &DeckEditorDatabaseDisplayWidget::databaseCustomMenu); - connect(databaseView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, - &DeckEditorDatabaseDisplayWidget::updateCard); - connect(databaseView, &QTreeView::doubleClicked, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - - QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState(); - if (dbHeaderState.isNull()) { - // first run - databaseView->setColumnWidth(0, 200); - } else { - databaseView->header()->restoreState(dbHeaderState); - } - connect(databaseView->header(), &QHeaderView::geometriesChanged, this, - &DeckEditorDatabaseDisplayWidget::saveDbHeaderState); searchEdit->setTreeView(databaseView); + searchEdit->installEventFilter(databaseView->getKeySignals()); + + connect(searchEdit, &SearchLineEdit::textChanged, databaseDisplayModel, &CardDatabaseDisplayModel::setStringFilter); + connect(databaseView, &CardDatabaseView::cardAdded, this, &DeckEditorDatabaseDisplayWidget::addCard); + connect(databaseView, &CardDatabaseView::cardDecremented, this, &DeckEditorDatabaseDisplayWidget::decrementCard); + connect(databaseView, &CardDatabaseView::cardChanged, this, &DeckEditorDatabaseDisplayWidget::updateCard); + + connect(databaseView, &CardDatabaseView::edhrecClicked, this, &DeckEditorDatabaseDisplayWidget::edhrecRequested); + connect(databaseView, &CardDatabaseView::selectPrintingClicked, this, + &DeckEditorDatabaseDisplayWidget::printingSelectorRequested); + connect(databaseView, &CardDatabaseView::relatedCardClicked, this, + &DeckEditorDatabaseDisplayWidget::onRelatedCardClicked); aAddCard = new QAction(QString(), this); aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green")); @@ -117,118 +82,39 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent retranslateUi(); } -void DeckEditorDatabaseDisplayWidget::updateSearch(const QString &search) -{ - databaseDisplayModel->setStringFilter(search); - QModelIndexList sel = databaseView->selectionModel()->selectedRows(); - if (sel.isEmpty() && databaseDisplayModel->rowCount()) - databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), - QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters() { databaseDisplayModel->clearFilterAll(); searchEdit->setText(""); } -void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/) -{ - if (!current.isValid()) { - return; - } - - const QString cardName = current.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); - - if (!current.model()->hasChildren(current.siblingAtColumn(CardDatabaseModel::NameColumn))) { - emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName)); - } -} - void DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck() { - emit addCardToMainDeck(currentCard()); + addCard(databaseView->currentCardName(), DECK_ZONE_MAIN); } void DeckEditorDatabaseDisplayWidget::actAddCardToSideboard() { - emit addCardToSideboard(currentCard()); + addCard(databaseView->currentCardName(), DECK_ZONE_SIDE); } -void DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck() +void DeckEditorDatabaseDisplayWidget::addCard(const QString &cardName, const QString &zoneName) { - emit decrementCardFromMainDeck(currentCard()); + highlightAllSearchEdit(); + ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName); + emit cardAdded(exactCard, zoneName); } -void DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard() +void DeckEditorDatabaseDisplayWidget::decrementCard(const QString &cardName, const QString &zoneName) { - emit decrementCardFromSideboard(currentCard()); + ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName); + emit cardDecremented(exactCard, zoneName); } -ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const +void DeckEditorDatabaseDisplayWidget::updateCard(const QString &cardName) { - const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) { - return {}; - } - - const QString cardName = currentIndex.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); - - return CardDatabaseManager::query()->getPreferredCard(cardName); -} - -void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point) -{ - QMenu menu; - ExactCard card = currentCard(); - - if (card) { - // add to deck and sideboard options - QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard; - addToDeck = menu.addAction(tr("Add to Deck")); - addToSideboard = menu.addAction(tr("Add to Sideboard")); - selectPrinting = menu.addAction(tr("Select Printing")); - connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); }); - if (canBeCommander(card.getInfo())) { - edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)")); - connect(edhRecCommander, &QAction::triggered, this, - [this, card] { deckEditor->getTabSupervisor()->addEdhrecTab(card.getCardPtr(), true); }); - } - edhRecCard = menu.addAction(tr("Show on EDHRec (Card)")); - - connect(addToDeck, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(addToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(edhRecCard, &QAction::triggered, this, - [this, card] { deckEditor->getTabSupervisor()->addEdhrecTab(card.getCardPtr()); }); - - // filling out the related cards submenu - auto *relatedMenu = new QMenu(tr("Show Related cards")); - menu.addMenu(relatedMenu); - auto relatedCards = card.getInfo().getAllRelatedCards(); - if (relatedCards.isEmpty()) { - relatedMenu->setDisabled(true); - } else { - for (const CardRelation *rel : relatedCards) { - const QString &relatedCardName = rel->getName(); - QAction *relatedCard = relatedMenu->addAction(relatedCardName); - connect( - relatedCard, &QAction::triggered, deckEditor->cardInfoDockWidget->cardInfo, - [this, relatedCardName] { deckEditor->cardInfoDockWidget->cardInfo->setCard(relatedCardName); }); - } - } - menu.exec(databaseView->mapToGlobal(point)); - } -} - -void DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents() -{ - auto _data = databaseView->selectionModel()->currentIndex().data(); - QApplication::clipboard()->setText(_data.toString()); -} - -void DeckEditorDatabaseDisplayWidget::saveDbHeaderState() -{ - SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState()); + ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName); + emit cardChanged(exactCard); } void DeckEditorDatabaseDisplayWidget::setFilterTree(FilterTree *filterTree) @@ -240,4 +126,15 @@ void DeckEditorDatabaseDisplayWidget::retranslateUi() { aAddCard->setText(tr("Add card to &maindeck")); aAddCardToSideboard->setText(tr("Add card to &sideboard")); +} + +void DeckEditorDatabaseDisplayWidget::highlightAllSearchEdit() +{ + searchEdit->setSelection(0, searchEdit->text().length()); +} + +void DeckEditorDatabaseDisplayWidget::onRelatedCardClicked(const QString &relatedCard) +{ + ExactCard exactCard = CardDatabaseManager::query()->guessCard({relatedCard}); + emit cardInfoRequested(exactCard); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h index 16ae6e255..5de4d211d 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h @@ -9,7 +9,6 @@ #define DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "../../key_signals.h" #include "../utility/custom_line_edit.h" #include @@ -17,54 +16,56 @@ #include #include +class CardDatabaseView; class AbstractTabDeckEditor; + class DeckEditorDatabaseDisplayWidget : public QWidget { Q_OBJECT public: - explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor); - AbstractTabDeckEditor *deckEditor; - SearchLineEdit *searchEdit; - CardDatabaseModel *databaseModel; - CardDatabaseDisplayModel *databaseDisplayModel; + explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, CardDatabaseModel *databaseModel); - QTreeView *getDatabaseView() + CardDatabaseView *getDatabaseView() const { return databaseView; } public slots: - ExactCard currentCard() const; void setFilterTree(FilterTree *filterTree); void clearAllDatabaseFilters(); - void updateSearch(const QString &search); - void updateCard(const QModelIndex ¤t, const QModelIndex &); + void actAddCardToMainDeck(); void actAddCardToSideboard(); - void actDecrementCardFromMainDeck(); - void actDecrementCardFromSideboard(); - void databaseCustomMenu(QPoint point); - void copyDatabaseCellContents(); + + void addCard(const QString &cardName, const QString &zoneName); + void decrementCard(const QString &cardName, const QString &zoneName); + void updateCard(const QString &cardName); signals: - void addCardToMainDeck(const ExactCard &card); - void addCardToSideboard(const ExactCard &card); - void decrementCardFromMainDeck(const ExactCard &card); - void decrementCardFromSideboard(const ExactCard &card); + void cardAdded(const ExactCard &card, const QString &zoneName); + void cardDecremented(const ExactCard &card, const QString &zoneName); void cardChanged(const ExactCard &_card); + void edhrecRequested(const CardInfoPtr &cardInfo, bool isCommander); + void printingSelectorRequested(); + void cardInfoRequested(const ExactCard &card); + private: - KeySignals searchKeySignals; - QTreeView *databaseView; + CardDatabaseDisplayModel *databaseDisplayModel; + CardDatabaseView *databaseView; QHBoxLayout *searchLayout; + SearchLineEdit *searchEdit; QAction *aAddCard, *aAddCardToSideboard; QVBoxLayout *centralFrame; QWidget *centralWidget; + void highlightAllSearchEdit(); + private slots: void retranslateUi(); - void saveDbHeaderState(); + + void onRelatedCardClicked(const QString &relatedCard); }; #endif // DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index f939ae99d..f751fa225 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -314,8 +314,9 @@ void DeckEditorDeckDockWidget::initializeFormats() ExactCard DeckEditorDeckDockWidget::getCurrentCard() { QModelIndex current = deckView->selectionModel()->currentIndex(); - if (!current.isValid()) + if (!current.isValid()) { return {}; + } const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); const QModelIndex gparent = current.parent().parent(); @@ -530,7 +531,7 @@ void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy) // currentIndex will return an index for the underlying deckModel instead of the proxy. // That index will return an invalid index when indexBelow/indexAbove crosses a header node, // causing the selection to fail to move down. - /// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround + //! \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround. if (deckViewCurrentIndex.model() == proxy->sourceModel()) { deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex); } @@ -634,8 +635,8 @@ void DeckEditorDeckDockWidget::actSwapSelection() { auto selectedRows = getSelectedCardNodeSourceIndices(); - // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted - // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted + //! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted. + // Hack: maintains old reselection behavior when single-selection row is deleted. if (selectedRows.length() == 1) { deckView->setSelectionMode(QAbstractItemView::SingleSelection); } @@ -651,10 +652,12 @@ void DeckEditorDeckDockWidget::actSwapSelection() void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName) { - if (!card) + if (!card) { return; - if (card.getInfo().getIsToken()) + } + if (card.getInfo().getIsToken()) { zoneName = DECK_ZONE_TOKENS; + } deckStateManager->decrementCard(card, zoneName); } @@ -663,8 +666,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection() { auto selectedRows = getSelectedCardNodeSourceIndices(); - // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted - // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted + //! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted. + // Hack: maintains old reselection behavior when single-selection row is deleted. if (selectedRows.length() == 1) { deckView->setSelectionMode(QAbstractItemView::SingleSelection); } @@ -680,8 +683,8 @@ void DeckEditorDeckDockWidget::actRemoveCard() { auto selectedRows = getSelectedCardNodeSourceIndices(); - // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted - // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted + //! \todo Remove the hack and also handle reselection when all rows of a multi-selection gets deleted. + // Hack: maintains old reselection behavior when single-selection row is deleted. if (selectedRows.length() == 1) { deckView->setSelectionMode(QAbstractItemView::SingleSelection); } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp index 8a9a6cdaa..6b18db82e 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -89,8 +89,9 @@ void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point QModelIndex idx; idx = filterView->indexAt(point); - if (!idx.isValid()) + if (!idx.isValid()) { return; + } action = menu.addAction(QString("delete")); action->setData(point); @@ -105,8 +106,9 @@ void DeckEditorFilterDockWidget::filterRemove(const QAction *action) point = action->data().toPoint(); idx = filterView->indexAt(point); - if (!idx.isValid()) + if (!idx.isValid()) { return; + } filterModel->removeRow(idx.row(), idx.parent()); } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 14c5faae7..e41529b22 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -8,8 +8,9 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { QModelIndex src = mapToSource(index); - if (!src.isValid()) + if (!src.isValid()) { return {}; + } QVariant value = QIdentityProxyModel::data(index, role); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp index 8da27b63c..f8fb450ce 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -192,8 +192,9 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName) { - if (!card) + if (!card) { return {}; + } QString providerId = card.getPrinting().getUuid(); QString collectorNumber = card.getPrinting().getProperty("num"); @@ -241,17 +242,23 @@ static bool doSwapCard(DeckListModel *model, bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx) { - if (!idx.isValid()) + if (!idx.isValid()) { return false; + } QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); QModelIndex gparent = idx.parent().parent(); - if (!gparent.isValid()) + if (!gparent.isValid()) { return false; + } QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + // tokens have no swap target + if (zoneName == DECK_ZONE_TOKENS) { + return false; + } QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; QString reason = tr("Moved to %1 1 × \"%2\" (%3)") // diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h index 4f1ec7e04..10312d0a0 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h @@ -154,9 +154,11 @@ public: */ QModelIndex modifyDeck(const QString &reason, const std::function &operation); - /// @name Metadata setters - /// @brief These methods set the metadata. Will no-op if the new value is the same as the current value. - /// Saves the operation to history if successful. + /** + * @name Metadata setters + * @brief These methods set the metadata. Will no-op if the new value is the same as the current value. + * Saves the operation to history if successful. + */ ///@{ void setName(const QString &name); void setComments(const QString &comments); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp index 0bb0eb1c9..fdbc90542 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp @@ -261,13 +261,7 @@ void DlgConnect::updateDisplayInfo(const QString &saveName) QStringList _data = uci.getServerInfo(saveName); if (_data.isEmpty()) { - _data << "" - << "" - << "" - << "" - << "" - << "" - << ""; + _data << "" << "" << "" << "" << "" << "" << ""; } bool savePasswordStatus = (_data.at(5) == "1"); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.h b/cockatrice/src/interface/widgets/dialogs/dlg_connect.h index 41993e068..083dad0ad 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.h @@ -1,8 +1,8 @@ /** * @file dlg_connect.h * @ingroup ConnectionDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_CONNECT_H #define DLG_CONNECT_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h index 61eea1d6e..6642ad8c6 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h @@ -2,8 +2,8 @@ * @file dlg_convert_deck_to_cod_format.h * @ingroup LocalDeckStorageDialogs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DIALOG_CONVERT_DECK_TO_COD_FORMAT_H #define DIALOG_CONVERT_DECK_TO_COD_FORMAT_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 7f109f600..30364f242 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -215,8 +215,9 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMapsetChecked(gameInfo.spectators_omniscient()); QSet types; - for (int i = 0; i < gameInfo.game_types_size(); ++i) + for (int i = 0; i < gameInfo.game_types_size(); ++i) { types.insert(gameInfo.game_types(i)); + } QMapIterator gameTypeIterator(gameTypes); while (gameTypeIterator.hasNext()) { @@ -316,9 +317,9 @@ void DlgCreateGame::checkResponse(const Response &response) { buttonBox->setEnabled(true); - if (response.response_code() == Response::RespOk) + if (response.response_code() == Response::RespOk) { accept(); - else { + } else { QMessageBox::critical(this, tr("Error"), tr("Server error.")); return; } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h index e28363311..61925286d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h @@ -1,8 +1,8 @@ /** * @file dlg_create_game.h * @ingroup RoomDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_CREATEGAME_H #define DLG_CREATEGAME_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp index 349f4ca4c..2d6ee7909 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp @@ -95,8 +95,9 @@ void DlgDefaultTagsEditor::addItem() // Prevent duplicate tags for (int i = 0; i < listWidget->count(); ++i) { QWidget *widget = listWidget->itemWidget(listWidget->item(i)); - if (!widget) + if (!widget) { continue; + } QLineEdit *lineEdit = widget->findChild(); if (lineEdit && lineEdit->text() == newTag) { QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists.")); @@ -138,8 +139,9 @@ void DlgDefaultTagsEditor::confirmChanges() QStringList updatedList; for (int i = 0; i < listWidget->count(); ++i) { QWidget *widget = listWidget->itemWidget(listWidget->item(i)); - if (!widget) + if (!widget) { continue; + } QLineEdit *lineEdit = widget->findChild(); if (lineEdit) { updatedList.append(lineEdit->text()); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h index 27af74105..e584d0731 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h @@ -2,8 +2,8 @@ * @file dlg_default_tags_editor.h * @ingroup Dialogs * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_DEFAULT_TAGS_EDITOR_H #define DLG_DEFAULT_TAGS_EDITOR_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.h index 641b736dc..d635ac2a8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.h @@ -1,8 +1,8 @@ /** * @file dlg_edit_avatar.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_EDITAVATAR_H #define DLG_EDITAVATAR_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp index e3bbc7435..cdd4433a7 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp @@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent) void DlgEditPassword::actOk() { - //! \todo this stuff should be using qvalidators + //! \todo This stuff should be using QValidators. if (newPasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h index ee8e45985..85c72233d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h @@ -1,8 +1,8 @@ /** * @file dlg_edit_password.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_EDITPASSWORD_H #define DLG_EDITPASSWORD_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index fe9cd181c..381aa2b11 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -149,8 +149,9 @@ void DlgEditTokens::actAddToken() QString name; for (;;) { name = getTextWithMax(this, tr("Add token"), tr("Please enter the name of the token:")); - if (name.isEmpty()) + if (name.isEmpty()) { return; + } if (databaseModel->getDatabase()->query()->getCardInfo(name)) { QMessageBox::critical(this, tr("Error"), tr("The chosen name conflicts with an existing card or token.\nMake sure to enable " @@ -181,18 +182,21 @@ void DlgEditTokens::actRemoveToken() void DlgEditTokens::colorChanged(int colorIndex) { - if (currentCard) + if (currentCard) { currentCard->setColors(QString(colorEdit->itemData(colorIndex).toChar())); + } } void DlgEditTokens::ptChanged(const QString &_pt) { - if (currentCard) + if (currentCard) { currentCard->setPowTough(_pt); + } } void DlgEditTokens::annotationChanged(const QString &_annotation) { - if (currentCard) + if (currentCard) { currentCard->setText(_annotation); + } } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.h index f19646756..202212e98 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.h @@ -1,8 +1,8 @@ /** * @file dlg_edit_tokens.h * @ingroup GameDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_EDIT_TOKENS_H #define DLG_EDIT_TOKENS_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp index dcfbc91e9..7015f9d47 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp @@ -26,8 +26,9 @@ DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QStrin int i = 1; for (const QString &c : countries) { countryEdit->addItem(QPixmap("theme:countries/" + c.toLower()), c); - if (c == country) + if (c == country) { countryEdit->setCurrentIndex(i); + } ++i; } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.h index b89a3be04..35a8bfb46 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.h @@ -1,8 +1,8 @@ /** * @file dlg_edit user.h * @ingroup NetworkDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_EDITUSER_H #define DLG_EDITUSER_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp index 1bf98822c..8f498cc2c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp @@ -22,41 +22,43 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, {QTime(1, 0), tr("1 hour")}, {QTime(2, 0), tr("2 hours")}}) { + const GameFilterConfigs &filters = gamesProxyModel->getFilters(); + hideBuddiesOnlyGames = new QCheckBox(tr("Hide 'buddies only' games")); - hideBuddiesOnlyGames->setChecked(gamesProxyModel->getHideBuddiesOnlyGames()); + hideBuddiesOnlyGames->setChecked(filters.hideBuddiesOnlyGames); hideFullGames = new QCheckBox(tr("Hide full games")); - hideFullGames->setChecked(gamesProxyModel->getHideFullGames()); + hideFullGames->setChecked(filters.hideFullGames); hideGamesThatStarted = new QCheckBox(tr("Hide games that have started")); - hideGamesThatStarted->setChecked(gamesProxyModel->getHideGamesThatStarted()); + hideGamesThatStarted->setChecked(filters.hideGamesThatStarted); hidePasswordProtectedGames = new QCheckBox(tr("Hide password protected games")); - hidePasswordProtectedGames->setChecked(gamesProxyModel->getHidePasswordProtectedGames()); + hidePasswordProtectedGames->setChecked(filters.hidePasswordProtectedGames); hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games")); - hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); + hideIgnoredUserGames->setChecked(filters.hideIgnoredUserGames); hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddies")); - hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames()); + hideNotBuddyCreatedGames->setChecked(filters.hideNotBuddyCreatedGames); hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists")); - hideOpenDecklistGames->setChecked(gamesProxyModel->getHideOpenDecklistGames()); + hideOpenDecklistGames->setChecked(filters.hideOpenDecklistGames); maxGameAgeComboBox = new QComboBox(); maxGameAgeComboBox->setEditable(false); maxGameAgeComboBox->addItems(gameAgeMap.values()); - QTime gameAge = gamesProxyModel->getMaxGameAge(); + QTime gameAge = filters.maxGameAge; maxGameAgeComboBox->setCurrentIndex(gameAgeMap.keys().indexOf(gameAge)); // index is -1 if unknown auto *maxGameAgeLabel = new QLabel(tr("&Newer than:")); maxGameAgeLabel->setBuddy(maxGameAgeComboBox); gameNameFilterEdit = new QLineEdit; - gameNameFilterEdit->setText(gamesProxyModel->getGameNameFilter()); + gameNameFilterEdit->setText(filters.gameNameFilter); auto *gameNameFilterLabel = new QLabel(tr("Game &description:")); gameNameFilterLabel->setBuddy(gameNameFilterEdit); creatorNameFilterEdit = new QLineEdit; - creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilters().join(", ")); + creatorNameFilterEdit->setText(filters.creatorNameFilters.join(", ")); auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); @@ -76,7 +78,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, gameTypesIterator.next(); auto *temp = new QCheckBox(gameTypesIterator.value()); - temp->setChecked(gamesProxyModel->getGameTypeFilter().contains(gameTypesIterator.key())); + temp->setChecked(filters.gameTypeFilter.contains(gameTypesIterator.key())); gameTypeFilterCheckBoxes.insert(gameTypesIterator.key(), temp); gameTypeFilterLayout->addWidget(temp); @@ -85,21 +87,22 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, if (!allGameTypes.isEmpty()) { gameTypeFilterGroupBox = new QGroupBox(tr("&Game types")); gameTypeFilterGroupBox->setLayout(gameTypeFilterLayout); - } else + } else { gameTypeFilterGroupBox = nullptr; + } auto *maxPlayersFilterMinLabel = new QLabel(tr("at &least:")); maxPlayersFilterMinSpinBox = new QSpinBox; maxPlayersFilterMinSpinBox->setMinimum(0); maxPlayersFilterMinSpinBox->setMaximum(99); - maxPlayersFilterMinSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMin()); + maxPlayersFilterMinSpinBox->setValue(filters.maxPlayersFilterMin); maxPlayersFilterMinLabel->setBuddy(maxPlayersFilterMinSpinBox); auto *maxPlayersFilterMaxLabel = new QLabel(tr("at &most:")); maxPlayersFilterMaxSpinBox = new QSpinBox; maxPlayersFilterMaxSpinBox->setMinimum(0); maxPlayersFilterMaxSpinBox->setMaximum(99); - maxPlayersFilterMaxSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMax()); + maxPlayersFilterMaxSpinBox->setValue(filters.maxPlayersFilterMax); maxPlayersFilterMaxLabel->setBuddy(maxPlayersFilterMaxSpinBox); auto *maxPlayersFilterLayout = new QGridLayout; @@ -124,17 +127,17 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, restrictionsGroupBox->setLayout(restrictionsLayout); showOnlyIfSpectatorsCanWatch = new QCheckBox(tr("Show games only if &spectators can watch")); - showOnlyIfSpectatorsCanWatch->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanWatch()); + showOnlyIfSpectatorsCanWatch->setChecked(filters.showOnlyIfSpectatorsCanWatch); connect(showOnlyIfSpectatorsCanWatch, &QCheckBox::toggled, this, &DlgFilterGames::toggleSpectatorCheckboxEnabledness); showSpectatorPasswordProtected = new QCheckBox(tr("Show spectator password p&rotected games")); - showSpectatorPasswordProtected->setChecked(gamesProxyModel->getShowSpectatorPasswordProtected()); + showSpectatorPasswordProtected->setChecked(filters.showSpectatorPasswordProtected); showOnlyIfSpectatorsCanChat = new QCheckBox(tr("Show only if spectators can ch&at")); - showOnlyIfSpectatorsCanChat->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanChat()); + showOnlyIfSpectatorsCanChat->setChecked(filters.showOnlyIfSpectatorsCanChat); showOnlyIfSpectatorsCanSeeHands = new QCheckBox(tr("Show only if spectators can see &hands")); - showOnlyIfSpectatorsCanSeeHands->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanSeeHands()); - toggleSpectatorCheckboxEnabledness(getShowOnlyIfSpectatorsCanWatch()); + showOnlyIfSpectatorsCanSeeHands->setChecked(filters.showOnlyIfSpectatorsCanSeeHands); + toggleSpectatorCheckboxEnabledness(filters.showOnlyIfSpectatorsCanWatch); auto *spectatorsLayout = new QGridLayout; spectatorsLayout->addWidget(showOnlyIfSpectatorsCanWatch, 0, 0); @@ -180,6 +183,27 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, setFixedHeight(sizeHint().height()); } +GameFilterConfigs DlgFilterGames::getFilters() const +{ + return {hideBuddiesOnlyGames->isChecked(), + hideIgnoredUserGames->isChecked(), + hideFullGames->isChecked(), + hideGamesThatStarted->isChecked(), + hidePasswordProtectedGames->isChecked(), + hideNotBuddyCreatedGames->isChecked(), + hideOpenDecklistGames->isChecked(), + gameNameFilterEdit->text(), + getCreatorNameFilters(), + getGameTypeFilter(), + maxPlayersFilterMinSpinBox->value(), + maxPlayersFilterMaxSpinBox->value(), + getMaxGameAge(), + showOnlyIfSpectatorsCanWatch->isChecked(), + getShowSpectatorPasswordProtected(), + getShowOnlyIfSpectatorsCanChat(), + getShowOnlyIfSpectatorsCanSeeHands()}; +} + void DlgFilterGames::actOk() { accept(); @@ -192,46 +216,6 @@ void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled) showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled); } -bool DlgFilterGames::getHideFullGames() const -{ - return hideFullGames->isChecked(); -} - -bool DlgFilterGames::getHideGamesThatStarted() const -{ - return hideGamesThatStarted->isChecked(); -} - -bool DlgFilterGames::getHideBuddiesOnlyGames() const -{ - return hideBuddiesOnlyGames->isChecked(); -} - -bool DlgFilterGames::getHidePasswordProtectedGames() const -{ - return hidePasswordProtectedGames->isChecked(); -} - -bool DlgFilterGames::getHideIgnoredUserGames() const -{ - return hideIgnoredUserGames->isChecked(); -} - -bool DlgFilterGames::getHideNotBuddyCreatedGames() const -{ - return hideNotBuddyCreatedGames->isChecked(); -} - -bool DlgFilterGames::getHideOpenDecklistGames() const -{ - return hideOpenDecklistGames->isChecked(); -} - -QString DlgFilterGames::getGameNameFilter() const -{ - return gameNameFilterEdit->text(); -} - QStringList DlgFilterGames::getCreatorNameFilters() const { return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts); @@ -243,36 +227,22 @@ QSet DlgFilterGames::getGameTypeFilter() const QMapIterator i(gameTypeFilterCheckBoxes); while (i.hasNext()) { i.next(); - if (i.value()->isChecked()) + if (i.value()->isChecked()) { result.insert(i.key()); + } } return result; } -int DlgFilterGames::getMaxPlayersFilterMin() const -{ - return maxPlayersFilterMinSpinBox->value(); -} - -int DlgFilterGames::getMaxPlayersFilterMax() const -{ - return maxPlayersFilterMaxSpinBox->value(); -} - QTime DlgFilterGames::getMaxGameAge() const { int index = maxGameAgeComboBox->currentIndex(); - if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds - return gamesProxyModel->getMaxGameAge(); // leave the setting unchanged + if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds + return gamesProxyModel->getFilters().maxGameAge; // leave the setting unchanged } return gameAgeMap.keys().at(index); } -bool DlgFilterGames::getShowOnlyIfSpectatorsCanWatch() const -{ - return showOnlyIfSpectatorsCanWatch->isChecked(); -} - bool DlgFilterGames::getShowSpectatorPasswordProtected() const { return showSpectatorPasswordProtected->isEnabled() && showSpectatorPasswordProtected->isChecked(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h index abfdab28a..447f9b16c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h @@ -1,8 +1,8 @@ /** * @file dlg_filter_games.h * @ingroup RoomDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_FILTER_GAMES_H #define DLG_FILTER_GAMES_H @@ -48,6 +48,14 @@ private: const QMap &allGameTypes; const GamesProxyModel *gamesProxyModel; + const QMap gameAgeMap; + + [[nodiscard]] QStringList getCreatorNameFilters() const; + [[nodiscard]] QSet getGameTypeFilter() const; + [[nodiscard]] QTime getMaxGameAge() const; + [[nodiscard]] bool getShowSpectatorPasswordProtected() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const; private slots: void actOk(); @@ -58,32 +66,7 @@ public: const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - [[nodiscard]] bool getHideFullGames() const; - [[nodiscard]] bool getHideGamesThatStarted() const; - [[nodiscard]] bool getHidePasswordProtectedGames() const; - void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); - [[nodiscard]] bool getHideBuddiesOnlyGames() const; - void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); - [[nodiscard]] bool getHideOpenDecklistGames() const; - void setHideOpenDecklistGames(bool _hideOpenDecklistGames); - [[nodiscard]] bool getHideIgnoredUserGames() const; - void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - [[nodiscard]] bool getHideNotBuddyCreatedGames() const; - [[nodiscard]] QString getGameNameFilter() const; - void setGameNameFilter(const QString &_gameNameFilter); - [[nodiscard]] QStringList getCreatorNameFilters() const; - void setCreatorNameFilter(const QString &_creatorNameFilter); - [[nodiscard]] QSet getGameTypeFilter() const; - void setGameTypeFilter(const QSet &_gameTypeFilter); - [[nodiscard]] int getMaxPlayersFilterMin() const; - [[nodiscard]] int getMaxPlayersFilterMax() const; - void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); - [[nodiscard]] QTime getMaxGameAge() const; - const QMap gameAgeMap; - [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const; - [[nodiscard]] bool getShowSpectatorPasswordProtected() const; - [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const; - [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const; + [[nodiscard]] GameFilterConfigs getFilters() const; }; #endif diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.h index e8925b1d3..d32e2fc3e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.h @@ -1,8 +1,8 @@ /** * @file dlg_forgot_password_challenge.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_FORGOTPASSWORDCHALLENGE_H #define DLG_FORGOTPASSWORDCHALLENGE_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h index e51dad810..2cf352e0d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h @@ -1,8 +1,8 @@ /** * @file dlg_forgot_password_request.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_FORGOTPASSWORDREQUEST_H #define DLG_FORGOTPASSWORDREQUEST_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp index c9c41722e..d2eb081d1 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp @@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk() return; } - //! \todo this stuff should be using qvalidators + //! \todo This stuff should be using QValidators. if (newpasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h index 3d110c71d..8a443b906 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h @@ -1,8 +1,8 @@ /** * @file dlg_forgot_password_reset.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_FORGOTPASSWORDRESET_H #define DLG_FORGOTPASSWORDRESET_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck.h index b103f6a56..27d658902 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck.h @@ -2,8 +2,8 @@ * @file dlg_load_deck.h * @ingroup LocalDeckStorageDialogs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_LOAD_DECK_H #define DLG_LOAD_DECK_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h index 0e59653ea..dce9d712c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h @@ -2,8 +2,8 @@ * @file dlg_load_deck_from_clipboard.h * @ingroup LocalDeckStorageDialogs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H #define DLG_LOAD_DECK_FROM_CLIPBOARD_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h index 1ac98d206..2890148b9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h @@ -2,8 +2,8 @@ * @file dlg_load_deck_from_website.h * @ingroup RemoteDeckStorageDialogs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_LOAD_DECK_FROM_WEBSITE_H #define DLG_LOAD_DECK_FROM_WEBSITE_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.h index b7e0d7aa3..9c81e9ae3 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.h @@ -2,8 +2,8 @@ * @file dlg_load_remote_deck.h * @ingroup RemoteDeckStorageDialogs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_STARTGAME_H #define DLG_STARTGAME_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index a4f54564f..c693fb02e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -216,8 +217,9 @@ void WndSets::saveHeaderState() void WndSets::rebuildMainLayout(int actionToTake) { - if (mainLayout == nullptr) + if (mainLayout == nullptr) { return; + } switch (actionToTake) { case NO_SETS_SELECTED: @@ -253,6 +255,11 @@ void WndSets::actSave() model->save(CardDatabaseManager::getInstance()); SettingsCache::instance().setIncludeRebalancedCards(includeRebalancedCards); CardPictureLoader::clearPixmapCache(); + const auto reloadOk1 = QtConcurrent::run([] { + CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); + + SettingsCache::instance().downloads().sync(); + }); close(); } @@ -376,12 +383,14 @@ void WndSets::actUp() std::sort(rows.begin(), rows.end(), std::less()); QSet newRows; - if (rows.empty()) + if (rows.empty()) { return; + } for (auto i : rows) { - if (i.row() <= 0) + if (i.row() <= 0) { continue; + } int oldRow = displayModel->mapToSource(i).row(); int newRow = i.row() - 1; @@ -399,12 +408,14 @@ void WndSets::actDown() std::sort(rows.begin(), rows.end(), [](const QModelIndex &a, const QModelIndex &b) { return b < a; }); QSet newRows; - if (rows.empty()) + if (rows.empty()) { return; + } for (auto i : rows) { - if (i.row() >= displayModel->rowCount() - 1) + if (i.row() >= displayModel->rowCount() - 1) { continue; + } int oldRow = displayModel->mapToSource(i).row(); int newRow = i.row() + 1; @@ -422,8 +433,9 @@ void WndSets::actTop() QSet newRows; int newRow = 0; - if (rows.empty()) + if (rows.empty()) { return; + } for (int i = 0; i < rows.length(); i++) { int oldRow = displayModel->mapToSource(rows.at(i)).row(); @@ -448,8 +460,9 @@ void WndSets::actBottom() QSet newRows; int newRow = model->rowCount() - 1; - if (rows.empty()) + if (rows.empty()) { return; + } for (int i = 0; i < rows.length(); i++) { int oldRow = displayModel->mapToSource(rows.at(i)).row(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h index d9b77a76e..2ed5eca0f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h @@ -1,8 +1,8 @@ /** * @file dlg_manage_sets.h * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_MANAGE_SETS_H #define DLG_MANAGE_SETS_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index a3f232d9b..0f7c17b18 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -311,8 +311,9 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) countryEdit->addItem(QPixmap("theme:countries/zw"), "zw"); countryEdit->setCurrentIndex(0); QStringList countries = SettingsCache::instance().getCountries(); - for (const QString &c : countries) + for (const QString &c : countries) { countryEdit->addItem(QPixmap("theme:countries/" + c.toLower()), c); + } realnameLabel = new QLabel(tr("Real name:")); realnameEdit = new QLineEdit(); @@ -356,7 +357,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) void DlgRegister::actOk() { - //! \todo this stuff should be using qvalidators + //! \todo This stuff should be using QValidators. if (passwordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.h b/cockatrice/src/interface/widgets/dialogs/dlg_register.h index bebbfc500..abed9ff51 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.h @@ -1,8 +1,8 @@ /** * @file dlg_register.h * @ingroup AccountDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_REGISTER_H #define DLG_REGISTER_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp index 5d9e2679c..bc846bf3d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp @@ -237,8 +237,9 @@ QMap DlgSelectSetForCards::getSetsForCards() for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); - if (!infoPtr) + if (!infoPtr) { continue; + } SetToPrintingsMap setMap = infoPtr->getSets(); for (auto &setName : setMap.keys()) { @@ -359,8 +360,9 @@ QMap DlgSelectSetForCards::getCardsForSets() for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); - if (!infoPtr) + if (!infoPtr) { continue; + } SetToPrintingsMap setMap = infoPtr->getSets(); for (auto it = setMap.begin(); it != setMap.end(); ++it) { diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h index 795366b57..92f285aa0 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h @@ -1,8 +1,8 @@ /** * @file dlg_select_set_for_cards.h * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_SELECT_SET_FOR_CARDS_H #define DLG_SELECT_SET_FOR_CARDS_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 6238bc80b..ff42596ea 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -1,1760 +1,29 @@ #include "dlg_settings.h" #include "../../../client/settings/cache_settings.h" -#include "../../../client/settings/shortcut_treeview.h" -#include "../client/network/update/card_spoiler/spoiler_background_updater.h" -#include "../client/network/update/client/release_channel.h" -#include "../client/sound_engine.h" -#include "../interface/card_picture_loader/card_picture_loader.h" -#include "../interface/theme_manager.h" -#include "../interface/widgets/general/background_sources.h" -#include "../interface/widgets/tabs/tab_supervisor.h" -#include "../interface/widgets/utility/custom_line_edit.h" -#include "../interface/widgets/utility/get_text_with_max.h" -#include "../interface/widgets/utility/sequence_edit.h" #include "../main.h" -#include "override_printing_warning.h" +#include "../settings_page/appearance_settings_page.h" +#include "../settings_page/deck_editor_settings_page.h" +#include "../settings_page/general_settings_page.h" +#include "../settings_page/messages_settings_page.h" +#include "../settings_page/shortcut_settings_page.h" +#include "../settings_page/sound_settings_page.h" +#include "../settings_page/storage_settings_page.h" +#include "../settings_page/user_interface_settings_page.h" +#include "libcockatrice/card/database/card_database_loader.h" +#include "libcockatrice/card/database/card_database_manager.h" -#include <../../client/settings/card_counter_settings.h> -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #include -#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include - -#define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs" -#define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts" -#define WIKI_TRANSLATION_FAQ "https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ" - -enum startupCardUpdateCheckBehaviorIndex -{ - startupCardUpdateCheckBehaviorIndexNone, - startupCardUpdateCheckBehaviorIndexPrompt, - startupCardUpdateCheckBehaviorIndexAlways -}; - -GeneralSettingsPage::GeneralSettingsPage() -{ - QStringList languageCodes = findQmFiles(); - for (const QString &code : languageCodes) { - QString langName = languageName(code); - languageBox.addItem(langName, code); - } - - QString setLanguage = QCoreApplication::translate("i18n", DEFAULT_LANG_NAME); - int index = languageBox.findText(setLanguage, Qt::MatchExactly); - if (index == -1) { - qWarning() << "could not find language" << setLanguage; - } else { - languageBox.setCurrentIndex(index); - } - - // updates - SettingsCache &settings = SettingsCache::instance(); - startupUpdateCheckCheckBox.setChecked(settings.getCheckUpdatesOnStartup()); - - startupCardUpdateCheckBehaviorSelector.addItem(""); // these will be set in retranslateUI - startupCardUpdateCheckBehaviorSelector.addItem(""); - startupCardUpdateCheckBehaviorSelector.addItem(""); - if (SettingsCache::instance().getStartupCardUpdateCheckPromptForUpdate()) { - startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexPrompt); - } else if (SettingsCache::instance().getStartupCardUpdateCheckAlwaysUpdate()) { - startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexAlways); - } else { - startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexNone); - } - - cardUpdateCheckIntervalSpinBox.setMinimum(1); - cardUpdateCheckIntervalSpinBox.setMaximum(30); - cardUpdateCheckIntervalSpinBox.setValue(settings.getCardUpdateCheckInterval()); - updateNotificationCheckBox.setChecked(settings.getNotifyAboutUpdates()); - newVersionOracleCheckBox.setChecked(settings.getNotifyAboutNewVersion()); - - showTipsOnStartup.setChecked(settings.getShowTipsOnStartup()); - - advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); - advertiseTranslationPageLabel.setOpenExternalLinks(true); - - connect(&languageBox, qOverload(&QComboBox::currentIndexChanged), this, - &GeneralSettingsPage::languageBoxChanged); - connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setCheckUpdatesOnStartup); - connect(&startupCardUpdateCheckBehaviorSelector, QOverload::of(&QComboBox::currentIndexChanged), this, - [](int index) { - SettingsCache::instance().setStartupCardUpdateCheckPromptForUpdate( - index == startupCardUpdateCheckBehaviorIndexPrompt); - SettingsCache::instance().setStartupCardUpdateCheckAlwaysUpdate( - index == startupCardUpdateCheckBehaviorIndexAlways); - }); - connect(&cardUpdateCheckIntervalSpinBox, qOverload(&QSpinBox::valueChanged), &settings, - &SettingsCache::setCardUpdateCheckInterval); - connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate); - connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setNotifyAboutNewVersion); - connect(&showTipsOnStartup, &QCheckBox::clicked, &settings, &SettingsCache::setShowTipsOnStartup); - - auto *personalGrid = new QGridLayout; - personalGrid->addWidget(&languageLabel, 0, 0); - personalGrid->addWidget(&languageBox, 0, 1); - personalGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight); - personalGrid->addWidget(&updateReleaseChannelLabel, 2, 0); - personalGrid->addWidget(&updateReleaseChannelBox, 2, 1); - personalGrid->addWidget(&startupUpdateCheckCheckBox, 4, 0, 1, 2); - personalGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 5, 0); - personalGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 5, 1); - personalGrid->addWidget(&cardUpdateCheckIntervalLabel, 6, 0); - personalGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 6, 1); - personalGrid->addWidget(&lastCardUpdateCheckDateLabel, 7, 1); - personalGrid->addWidget(&updateNotificationCheckBox, 8, 0, 1, 2); - personalGrid->addWidget(&newVersionOracleCheckBox, 9, 0, 1, 2); - personalGrid->addWidget(&showTipsOnStartup, 10, 0, 1, 2); - - personalGroupBox = new QGroupBox; - personalGroupBox->setLayout(personalGrid); - - deckPathEdit = new QLineEdit(settings.getDeckPath()); - deckPathEdit->setReadOnly(true); - auto *deckPathButton = new QPushButton("..."); - connect(deckPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::deckPathButtonClicked); - - filtersPathEdit = new QLineEdit(settings.getFiltersPath()); - filtersPathEdit->setReadOnly(true); - auto *filtersPathButton = new QPushButton("..."); - connect(filtersPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::filtersPathButtonClicked); - - replaysPathEdit = new QLineEdit(settings.getReplaysPath()); - replaysPathEdit->setReadOnly(true); - auto *replaysPathButton = new QPushButton("..."); - connect(replaysPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::replaysPathButtonClicked); - - picsPathEdit = new QLineEdit(settings.getPicsPath()); - picsPathEdit->setReadOnly(true); - auto *picsPathButton = new QPushButton("..."); - connect(picsPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::picsPathButtonClicked); - - cardDatabasePathEdit = new QLineEdit(settings.getCardDatabasePath()); - cardDatabasePathEdit->setReadOnly(true); - auto *cardDatabasePathButton = new QPushButton("..."); - connect(cardDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::cardDatabasePathButtonClicked); - - customCardDatabasePathEdit = new QLineEdit(settings.getCustomCardDatabasePath()); - customCardDatabasePathEdit->setReadOnly(true); - auto *customCardDatabasePathButton = new QPushButton("..."); - connect(customCardDatabasePathButton, &QPushButton::clicked, this, - &GeneralSettingsPage::customCardDatabaseButtonClicked); - - tokenDatabasePathEdit = new QLineEdit(settings.getTokenDatabasePath()); - tokenDatabasePathEdit->setReadOnly(true); - auto *tokenDatabasePathButton = new QPushButton("..."); - connect(tokenDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::tokenDatabasePathButtonClicked); - - // Required init here to avoid crashing on Portable builds - resetAllPathsButton = new QPushButton; - - bool isPortable = settings.getIsPortableBuild(); - if (isPortable) { - deckPathEdit->setEnabled(false); - filtersPathEdit->setEnabled(false); - replaysPathEdit->setEnabled(false); - picsPathEdit->setEnabled(false); - cardDatabasePathEdit->setEnabled(false); - customCardDatabasePathEdit->setEnabled(false); - tokenDatabasePathEdit->setEnabled(false); - - deckPathButton->setVisible(false); - replaysPathButton->setVisible(false); - picsPathButton->setVisible(false); - cardDatabasePathButton->setVisible(false); - customCardDatabasePathButton->setVisible(false); - tokenDatabasePathButton->setVisible(false); - } else { - connect(resetAllPathsButton, &QPushButton::clicked, this, &GeneralSettingsPage::resetAllPathsClicked); - allPathsResetLabel = new QLabel(tr("All paths have been reset")); - allPathsResetLabel->setVisible(false); - } - - auto *pathsGrid = new QGridLayout; - pathsGrid->addWidget(&deckPathLabel, 0, 0); - pathsGrid->addWidget(deckPathEdit, 0, 1); - pathsGrid->addWidget(deckPathButton, 0, 2); - pathsGrid->addWidget(&filtersPathLabel, 1, 0); - pathsGrid->addWidget(filtersPathEdit, 1, 1); - pathsGrid->addWidget(filtersPathButton, 1, 2); - pathsGrid->addWidget(&replaysPathLabel, 2, 0); - pathsGrid->addWidget(replaysPathEdit, 2, 1); - pathsGrid->addWidget(replaysPathButton, 2, 2); - pathsGrid->addWidget(&picsPathLabel, 3, 0); - pathsGrid->addWidget(picsPathEdit, 3, 1); - pathsGrid->addWidget(picsPathButton, 3, 2); - pathsGrid->addWidget(&cardDatabasePathLabel, 4, 0); - pathsGrid->addWidget(cardDatabasePathEdit, 4, 1); - pathsGrid->addWidget(cardDatabasePathButton, 4, 2); - pathsGrid->addWidget(&customCardDatabasePathLabel, 5, 0); - pathsGrid->addWidget(customCardDatabasePathEdit, 5, 1); - pathsGrid->addWidget(customCardDatabasePathButton, 5, 2); - pathsGrid->addWidget(&tokenDatabasePathLabel, 6, 0); - pathsGrid->addWidget(tokenDatabasePathEdit, 6, 1); - pathsGrid->addWidget(tokenDatabasePathButton, 6, 2); - if (!isPortable) { - pathsGrid->addWidget(resetAllPathsButton, 7, 0); - pathsGrid->addWidget(allPathsResetLabel, 7, 1); - } - pathsGroupBox = new QGroupBox; - pathsGroupBox->setLayout(pathsGrid); - - auto *mainLayout = new QVBoxLayout; - mainLayout->addWidget(personalGroupBox); - mainLayout->addWidget(pathsGroupBox); - mainLayout->addStretch(); - - GeneralSettingsPage::retranslateUi(); - - // connect the ReleaseChannel combo box only after the entries are inserted in retranslateUi - connect(&updateReleaseChannelBox, qOverload(&QComboBox::currentIndexChanged), &settings, - &SettingsCache::setUpdateReleaseChannelIndex); - updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex()); - - setLayout(mainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &GeneralSettingsPage::retranslateUi); - retranslateUi(); -} - -QStringList GeneralSettingsPage::findQmFiles() -{ - QDir dir(translationPath); - QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name); - fileNames.replaceInStrings(QRegularExpression(translationPrefix + "_(.*)\\.qm"), "\\1"); - return fileNames; -} - -QString GeneralSettingsPage::languageName(const QString &lang) -{ - QTranslator qTranslator; - - QString appNameHint = translationPrefix + "_" + lang; - bool appTranslationLoaded = qTranslator.load(appNameHint, translationPath); - if (!appTranslationLoaded) { - qCWarning(DlgSettingsLog) << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" - << translationPath; - } - - return qTranslator.translate("i18n", DEFAULT_LANG_NAME); -} - -void GeneralSettingsPage::deckPathButtonClicked() -{ - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), deckPathEdit->text()); - if (path.isEmpty()) - return; - - deckPathEdit->setText(path); - SettingsCache::instance().setDeckPath(path); -} - -void GeneralSettingsPage::filtersPathButtonClicked() -{ - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), filtersPathEdit->text()); - if (path.isEmpty()) - return; - - filtersPathEdit->setText(path); - SettingsCache::instance().setFiltersPath(path); -} - -void GeneralSettingsPage::replaysPathButtonClicked() -{ - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text()); - if (path.isEmpty()) - return; - - replaysPathEdit->setText(path); - SettingsCache::instance().setReplaysPath(path); -} - -void GeneralSettingsPage::picsPathButtonClicked() -{ - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), picsPathEdit->text()); - if (path.isEmpty()) - return; - - picsPathEdit->setText(path); - SettingsCache::instance().setPicsPath(path); -} - -void GeneralSettingsPage::cardDatabasePathButtonClicked() -{ - QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), cardDatabasePathEdit->text()); - if (path.isEmpty()) - return; - - cardDatabasePathEdit->setText(path); - SettingsCache::instance().setCardDatabasePath(path); -} - -void GeneralSettingsPage::customCardDatabaseButtonClicked() -{ - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), customCardDatabasePathEdit->text()); - if (path.isEmpty()) - return; - - customCardDatabasePathEdit->setText(path); - SettingsCache::instance().setCustomCardDatabasePath(path); -} - -void GeneralSettingsPage::tokenDatabasePathButtonClicked() -{ - QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), tokenDatabasePathEdit->text()); - if (path.isEmpty()) - return; - - tokenDatabasePathEdit->setText(path); - SettingsCache::instance().setTokenDatabasePath(path); -} - -void GeneralSettingsPage::resetAllPathsClicked() -{ - SettingsCache &settings = SettingsCache::instance(); - settings.resetPaths(); - deckPathEdit->setText(settings.getDeckPath()); - replaysPathEdit->setText(settings.getReplaysPath()); - picsPathEdit->setText(settings.getPicsPath()); - cardDatabasePathEdit->setText(settings.getCardDatabasePath()); - customCardDatabasePathEdit->setText(settings.getCustomCardDatabasePath()); - tokenDatabasePathEdit->setText(settings.getTokenDatabasePath()); - allPathsResetLabel->setVisible(true); -} - -void GeneralSettingsPage::languageBoxChanged(int index) -{ - SettingsCache::instance().setLang(languageBox.itemData(index).toString()); -} - -void GeneralSettingsPage::retranslateUi() -{ - personalGroupBox->setTitle(tr("Personal settings")); - languageLabel.setText(tr("Language:")); - - if (SettingsCache::instance().getIsPortableBuild()) { - pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)")); - } else { - pathsGroupBox->setTitle(tr("Paths")); - } - advertiseTranslationPageLabel.setText( - QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); - deckPathLabel.setText(tr("Decks directory:")); - filtersPathLabel.setText(tr("Filters directory:")); - replaysPathLabel.setText(tr("Replays directory:")); - picsPathLabel.setText(tr("Pictures directory:")); - cardDatabasePathLabel.setText(tr("Card database:")); - customCardDatabasePathLabel.setText(tr("Custom database directory:")); - tokenDatabasePathLabel.setText(tr("Token database:")); - updateReleaseChannelLabel.setText(tr("Update channel")); - startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup")); - startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup")); - startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check")); - startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt, - tr("Prompt for update")); - startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexAlways, - tr("Always update in the background")); - cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every")); - cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days")); - updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); - newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); - showTipsOnStartup.setText(tr("Show tips on startup")); - resetAllPathsButton->setText(tr("Reset all paths")); - - const auto &settings = SettingsCache::instance(); - - QDate lastCheckDate = settings.getLastCardUpdateCheck(); - int daysAgo = lastCheckDate.daysTo(QDate::currentDate()); - - lastCardUpdateCheckDateLabel.setText( - tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo)); - - // We can't change the strings after they're put into the QComboBox, so this is our workaround - int oldIndex = updateReleaseChannelBox.currentIndex(); - updateReleaseChannelBox.clear(); - for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) { - updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8())); - } - updateReleaseChannelBox.setCurrentIndex(oldIndex); -} - -AppearanceSettingsPage::AppearanceSettingsPage() -{ - SettingsCache &settings = SettingsCache::instance(); - - // Theme settings - QString themeName = SettingsCache::instance().getThemeName(); - - QStringList themeDirs = themeManager->getAvailableThemes().keys(); - for (int i = 0; i < themeDirs.size(); i++) { - themeBox.addItem(themeDirs[i]); - if (themeDirs[i] == themeName) - themeBox.setCurrentIndex(i); - } - - connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &AppearanceSettingsPage::themeBoxChanged); - connect(&openThemeButton, &QPushButton::clicked, this, &AppearanceSettingsPage::openThemeLocation); - - for (const auto &entry : BackgroundSources::all()) { - homeTabBackgroundSourceBox.addItem(QObject::tr(entry.trKey), QVariant::fromValue(entry.type)); - } - - QString homeTabBackgroundSource = SettingsCache::instance().getHomeTabBackgroundSource(); - int homeTabBackgroundSourceId = - homeTabBackgroundSourceBox.findData(BackgroundSources::fromId(homeTabBackgroundSource)); - if (homeTabBackgroundSourceId != -1) { - homeTabBackgroundSourceBox.setCurrentIndex(homeTabBackgroundSourceId); - } - - connect(&homeTabBackgroundSourceBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { - auto type = homeTabBackgroundSourceBox.currentData().value(); - SettingsCache::instance().setHomeTabBackgroundSource(BackgroundSources::toId(type)); - updateHomeTabSettingsVisibility(); - }); - - homeTabBackgroundShuffleFrequencySpinBox.setRange(0, 3600); - homeTabBackgroundShuffleFrequencySpinBox.setSuffix(tr(" seconds")); - homeTabBackgroundShuffleFrequencySpinBox.setValue(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency()); - connect(&homeTabBackgroundShuffleFrequencySpinBox, qOverload(&QSpinBox::valueChanged), - &SettingsCache::instance(), &SettingsCache::setHomeTabBackgroundShuffleFrequency); - - homeTabDisplayCardNameCheckBox.setChecked(settings.getHomeTabDisplayCardName()); - connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setHomeTabDisplayCardName); - - updateHomeTabSettingsVisibility(); - - auto *themeGrid = new QGridLayout; - themeGrid->addWidget(&themeLabel, 0, 0); - themeGrid->addWidget(&themeBox, 0, 1); - themeGrid->addWidget(&openThemeButton, 1, 1); - themeGrid->addWidget(&homeTabBackgroundSourceLabel, 2, 0); - themeGrid->addWidget(&homeTabBackgroundSourceBox, 2, 1); - themeGrid->addWidget(&homeTabBackgroundShuffleFrequencyLabel, 3, 0); - themeGrid->addWidget(&homeTabBackgroundShuffleFrequencySpinBox, 3, 1); - themeGrid->addWidget(&homeTabDisplayCardNameLabel, 4, 0); - themeGrid->addWidget(&homeTabDisplayCardNameCheckBox, 4, 1); - - themeGroupBox = new QGroupBox; - themeGroupBox->setLayout(themeGrid); - - // Menu settings - showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); - connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); - - showGameSelectorFilterToolbarCheckBox.setChecked(settings.getShowGameSelectorFilterToolbar()); - connect(&showGameSelectorFilterToolbarCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setShowGameSelectorFilterToolbar); - - auto *menuGrid = new QGridLayout; - menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); - menuGrid->addWidget(&showGameSelectorFilterToolbarCheckBox, 1, 0); - - menuGroupBox = new QGroupBox; - menuGroupBox->setLayout(menuGrid); - - // Card rendering - displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames()); - connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames); - - autoRotateSidewaysLayoutCardsCheckBox.setChecked(settings.getAutoRotateSidewaysLayoutCards()); - connect(&autoRotateSidewaysLayoutCardsCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setAutoRotateSidewaysLayoutCards); - - overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(settings.getOverrideAllCardArtWithPersonalPreference()); - connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, this, - &AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled); - - bumpSetsWithCardsInDeckToTopCheckBox.setChecked(settings.getBumpSetsWithCardsInDeckToTop()); - connect(&bumpSetsWithCardsInDeckToTopCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setBumpSetsWithCardsInDeckToTop); - - cardScalingCheckBox.setChecked(settings.getScaleCards()); - connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling); - - roundCardCornersCheckBox.setChecked(settings.getRoundCardCorners()); - connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings, &SettingsCache::setRoundCardCorners); - - verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); - verticalCardOverlapPercentBox.setRange(0, 80); - connect(&verticalCardOverlapPercentBox, qOverload(&QSpinBox::valueChanged), &settings, - &SettingsCache::setStackCardOverlapPercent); - - cardViewInitialRowsMaxBox.setRange(1, 999); - cardViewInitialRowsMaxBox.setValue(SettingsCache::instance().getCardViewInitialRowsMax()); - connect(&cardViewInitialRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, - &AppearanceSettingsPage::cardViewInitialRowsMaxChanged); - - cardViewExpandedRowsMaxBox.setRange(1, 999); - cardViewExpandedRowsMaxBox.setValue(SettingsCache::instance().getCardViewExpandedRowsMax()); - connect(&cardViewExpandedRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, - &AppearanceSettingsPage::cardViewExpandedRowsMaxChanged); - - auto *cardsGrid = new QGridLayout; - cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); - cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); - cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); - cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); - cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 4, 0, 1, 2); - cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 5, 0, 1, 2); - cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 6, 0, 1, 1); - cardsGrid->addWidget(&verticalCardOverlapPercentBox, 6, 1, 1, 1); - cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 7, 0); - cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 7, 1); - cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 8, 0); - cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 8, 1); - - cardsGroupBox = new QGroupBox; - cardsGroupBox->setLayout(cardsGrid); - - // Card counter colors - - auto *cardCounterColorsLayout = new QGridLayout; - cardCounterColorsLayout->setColumnStretch(1, 1); - cardCounterColorsLayout->setColumnStretch(3, 1); - cardCounterColorsLayout->setColumnStretch(5, 1); - - auto &cardCounterSettings = SettingsCache::instance().cardCounters(); - for (int index = 0; index < 6; ++index) { - auto *pushButton = new QPushButton; - pushButton->setStyleSheet(QString("background-color: %1").arg(cardCounterSettings.color(index).name())); - - connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, pushButton, - [index, pushButton](int changedIndex, const QColor &color) { - if (index == changedIndex) { - pushButton->setStyleSheet(QString("background-color: %1").arg(color.name())); - } - }); - - connect(pushButton, &QPushButton::clicked, this, [index, this]() { - auto &cardCounterSettings = SettingsCache::instance().cardCounters(); - - auto newColor = QColorDialog::getColor(cardCounterSettings.color(index), this); - if (!newColor.isValid()) - return; - - cardCounterSettings.setColor(index, newColor); - }); - - auto *colorName = new QLabel; - cardCounterNames.append(colorName); - - int row = index / 3; - int column = 2 * (index % 3); - - cardCounterColorsLayout->addWidget(pushButton, row, column); - cardCounterColorsLayout->addWidget(colorName, row, column + 1); - } - - auto *cardCountersLayout = new QVBoxLayout; - cardCountersLayout->addLayout(cardCounterColorsLayout, 1); - - cardCountersGroupBox = new QGroupBox; - cardCountersGroupBox->setLayout(cardCountersLayout); - - // Hand layout - horizontalHandCheckBox.setChecked(settings.getHorizontalHand()); - connect(&horizontalHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setHorizontalHand); - - leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified()); - connect(&leftJustifiedHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setLeftJustified); - - auto *handGrid = new QGridLayout; - handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2); - handGrid->addWidget(&leftJustifiedHandCheckBox, 1, 0, 1, 2); - - handGroupBox = new QGroupBox; - handGroupBox->setLayout(handGrid); - - // table grid layout - invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate()); - connect(&invertVerticalCoordinateCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setInvertVerticalCoordinate); - - minPlayersForMultiColumnLayoutEdit.setMinimum(2); - minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); - connect(&minPlayersForMultiColumnLayoutEdit, qOverload(&QSpinBox::valueChanged), &settings, - &SettingsCache::setMinPlayersForMultiColumnLayout); - minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit); - - connect(&maxFontSizeForCardsEdit, qOverload(&QSpinBox::valueChanged), &settings, - &SettingsCache::setMaxFontSize); - maxFontSizeForCardsEdit.setValue(settings.getMaxFontSize()); - maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit); - maxFontSizeForCardsEdit.setMinimum(9); - maxFontSizeForCardsEdit.setMaximum(100); - - auto *tableGrid = new QGridLayout; - tableGrid->addWidget(&invertVerticalCoordinateCheckBox, 0, 0, 1, 2); - tableGrid->addWidget(&minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1); - tableGrid->addWidget(&minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1); - tableGrid->addWidget(&maxFontSizeForCardsLabel, 2, 0, 1, 1); - tableGrid->addWidget(&maxFontSizeForCardsEdit, 2, 1, 1, 1); - - tableGroupBox = new QGroupBox; - tableGroupBox->setLayout(tableGrid); - - // putting it all together - auto *mainLayout = new QVBoxLayout; - mainLayout->addWidget(themeGroupBox); - mainLayout->addWidget(menuGroupBox); - mainLayout->addWidget(cardsGroupBox); - mainLayout->addWidget(cardCountersGroupBox); - mainLayout->addWidget(handGroupBox); - mainLayout->addWidget(tableGroupBox); - mainLayout->addStretch(); - - setLayout(mainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &AppearanceSettingsPage::retranslateUi); - retranslateUi(); -} - -void AppearanceSettingsPage::themeBoxChanged(int index) -{ - QStringList themeDirs = themeManager->getAvailableThemes().keys(); - if (index >= 0 && index < themeDirs.count()) - SettingsCache::instance().setThemeName(themeDirs.at(index)); -} - -void AppearanceSettingsPage::openThemeLocation() -{ - QString dir = SettingsCache::instance().getThemesPath(); - QDir dirDir = dir; - dirDir.cdUp(); - // open if dir exists, create if parent dir does exist - if (dirDir.exists() && dirDir.mkpath(dir)) { - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); - } else { - QMessageBox::critical(this, tr("Error"), tr("Could not create themes directory at '%1'.").arg(dir)); - } -} - -void AppearanceSettingsPage::updateHomeTabSettingsVisibility() -{ - bool visible = - SettingsCache::instance().getHomeTabBackgroundSource() != BackgroundSources::toId(BackgroundSources::Theme); - - homeTabBackgroundShuffleFrequencyLabel.setVisible(visible); - homeTabBackgroundShuffleFrequencySpinBox.setVisible(visible); - homeTabDisplayCardNameLabel.setVisible(visible); - homeTabDisplayCardNameCheckBox.setVisible(visible); -} - -void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value) -{ - SettingsCache::instance().setShowShortcuts(value); - qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, value == 0); // 0 = unchecked -} - -void AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T value) -{ - bool enable = static_cast(value); - - bool accepted = OverridePrintingWarning::execMessageBox(this, enable); - - if (!accepted) { - // If user cancels, revert the checkbox/state back - QTimer::singleShot(0, this, [this, enable]() { - overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(true); - overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(!enable); - overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(false); - }); - } -} - -/** - * Updates the settings for cardViewInitialRowsMax. - * Forces expanded rows max to always be >= initial rows max - * @param value The new value - */ -void AppearanceSettingsPage::cardViewInitialRowsMaxChanged(int value) -{ - SettingsCache::instance().setCardViewInitialRowsMax(value); - if (cardViewExpandedRowsMaxBox.value() < value) { - cardViewExpandedRowsMaxBox.setValue(value); - } -} - -/** - * Updates the settings for cardViewExpandedRowsMax. - * Forces initial rows max to always be <= expanded rows max - * @param value The new value - */ -void AppearanceSettingsPage::cardViewExpandedRowsMaxChanged(int value) -{ - SettingsCache::instance().setCardViewExpandedRowsMax(value); - if (cardViewInitialRowsMaxBox.value() > value) { - cardViewInitialRowsMaxBox.setValue(value); - } -} - -void AppearanceSettingsPage::retranslateUi() -{ - themeGroupBox->setTitle(tr("Theme settings")); - themeLabel.setText(tr("Current theme:")); - openThemeButton.setText(tr("Open themes folder")); - homeTabBackgroundSourceLabel.setText(tr("Home tab background source:")); - homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:")); - homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled")); - homeTabDisplayCardNameLabel.setText(tr("Display card name of background in bottom right:")); - - menuGroupBox->setTitle(tr("Menu settings")); - showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); - showGameSelectorFilterToolbarCheckBox.setText(tr("Show game filter toolbar above list in room tab")); - - cardsGroupBox->setTitle(tr("Card rendering")); - displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); - autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); - overrideAllCardArtWithPersonalPreferenceCheckBox.setText( - tr("Override all card art with personal set preference (Pre-ProviderID change behavior)")); - bumpSetsWithCardsInDeckToTopCheckBox.setText( - tr("Bump sets that the deck contains cards from to the top in the printing selector")); - cardScalingCheckBox.setText(tr("Scale cards on mouse over")); - roundCardCornersCheckBox.setText(tr("Use rounded card corners")); - verticalCardOverlapPercentLabel.setText( - tr("Minimum overlap percentage of cards on the stack and in vertical hand")); - cardViewInitialRowsMaxLabel.setText(tr("Maximum initial height for card view window:")); - cardViewInitialRowsMaxBox.setSuffix(tr(" rows")); - cardViewExpandedRowsMaxLabel.setText(tr("Maximum expanded height for card view window:")); - cardViewExpandedRowsMaxBox.setSuffix(tr(" rows")); - - cardCountersGroupBox->setTitle(tr("Card counters")); - - auto &cardCounterSettings = SettingsCache::instance().cardCounters(); - for (int index = 0; index < cardCounterNames.size(); ++index) { - cardCounterNames[index]->setText(tr("Counter %1").arg(cardCounterSettings.displayName(index))); - } - - handGroupBox->setTitle(tr("Hand layout")); - horizontalHandCheckBox.setText(tr("Display hand horizontally (wastes space)")); - leftJustifiedHandCheckBox.setText(tr("Enable left justification")); - - tableGroupBox->setTitle(tr("Table grid layout")); - invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate")); - minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:")); - maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:")); -} - -enum visualDeckStoragePromptForConversionIndex -{ - visualDeckStoragePromptForConversionIndexNone, - visualDeckStoragePromptForConversionIndexPrompt, - visualDeckStoragePromptForConversionIndexAlways -}; - -UserInterfaceSettingsPage::UserInterfaceSettingsPage() -{ - // general settings and notification settings - notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled()); - connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setNotificationsEnabled); - connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this, - &UserInterfaceSettingsPage::setNotificationEnabled); - - specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled()); - specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setSpectatorNotificationsEnabled); - - buddyConnectNotificationsEnabledCheckBox.setChecked( - SettingsCache::instance().getBuddyConnectNotificationsEnabled()); - buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setBuddyConnectNotificationsEnabled); - - doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay()); - connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setDoubleClickToPlay); - - clickPlaysAllSelectedCheckBox.setChecked(SettingsCache::instance().getClickPlaysAllSelected()); - connect(&clickPlaysAllSelectedCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setClickPlaysAllSelected); - - playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); - connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setPlayToStack); - - doNotDeleteArrowsInSubPhasesCheckBox.setChecked(SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()); - connect(&doNotDeleteArrowsInSubPhasesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setDoNotDeleteArrowsInSubPhases); - - closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); - connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setCloseEmptyCardView); - - focusCardViewSearchBarCheckBox.setChecked(SettingsCache::instance().getFocusCardViewSearchBar()); - connect(&focusCardViewSearchBarCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setFocusCardViewSearchBar); - - annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); - connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setAnnotateTokens); - - showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); - connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowDragSelectionCount); - - showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); - connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowTotalSelectionCount); - - useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); - connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); - - auto *generalGrid = new QGridLayout; - generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); - generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); - generalGrid->addWidget(&playToStackCheckBox, 2, 0); - generalGrid->addWidget(&doNotDeleteArrowsInSubPhasesCheckBox, 3, 0); - generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); - generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); - generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); - generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); - - generalGroupBox = new QGroupBox; - generalGroupBox->setLayout(generalGrid); - - auto *notificationsGrid = new QGridLayout; - notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0); - notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0); - notificationsGrid->addWidget(&buddyConnectNotificationsEnabledCheckBox, 2, 0); - - notificationsGroupBox = new QGroupBox; - notificationsGroupBox->setLayout(notificationsGrid); - - // animation settings - tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation()); - connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setTapAnimation); - - auto *animationGrid = new QGridLayout; - animationGrid->addWidget(&tapAnimationCheckBox, 0, 0); - - animationGroupBox = new QGroupBox; - animationGroupBox->setLayout(animationGrid); - - // deck editor settings - openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab()); - connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setOpenDeckInNewTab); - - visualDeckStorageInGameCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageInGame()); - connect(&visualDeckStorageInGameCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageInGame); - - visualDeckStorageSelectionAnimationCheckBox.setChecked( - SettingsCache::instance().getVisualDeckStorageSelectionAnimation()); - connect(&visualDeckStorageSelectionAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setVisualDeckStorageSelectionAnimation); - - visualDeckStoragePromptForConversionSelector.addItem(""); // these will be set in retranslateUI - visualDeckStoragePromptForConversionSelector.addItem(""); - visualDeckStoragePromptForConversionSelector.addItem(""); - if (SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { - visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexPrompt); - } else if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { - visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexAlways); - } else { - visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexNone); - } - connect(&visualDeckStoragePromptForConversionSelector, QOverload::of(&QComboBox::currentIndexChanged), this, - [](int index) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion( - index == visualDeckStoragePromptForConversionIndexPrompt); - SettingsCache::instance().setVisualDeckStorageAlwaysConvert( - index == visualDeckStoragePromptForConversionIndexAlways); - }); - - defaultDeckEditorTypeSelector.addItem(""); // these will be set in retranslateUI - defaultDeckEditorTypeSelector.addItem(""); - defaultDeckEditorTypeSelector.setCurrentIndex(SettingsCache::instance().getDefaultDeckEditorType()); - connect(&defaultDeckEditorTypeSelector, QOverload::of(&QComboBox::currentIndexChanged), - &SettingsCache::instance(), &SettingsCache::setDefaultDeckEditorType); - - auto *deckEditorGrid = new QGridLayout; - deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); - deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0); - deckEditorGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 2, 0); - deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 3, 0); - deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); - deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0); - deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1); - - deckEditorGroupBox = new QGroupBox; - deckEditorGroupBox->setLayout(deckEditorGrid); - - // replay settings - rewindBufferingMsBox.setRange(0, 9999); - rewindBufferingMsBox.setValue(SettingsCache::instance().getRewindBufferingMs()); - connect(&rewindBufferingMsBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), - &SettingsCache::setRewindBufferingMs); - - auto *replayGrid = new QGridLayout; - replayGrid->addWidget(&rewindBufferingMsLabel, 0, 0, 1, 1); - replayGrid->addWidget(&rewindBufferingMsBox, 0, 1, 1, 1); - - replayGroupBox = new QGroupBox; - replayGroupBox->setLayout(replayGrid); - - // putting it all together - auto *mainLayout = new QVBoxLayout; - mainLayout->addWidget(generalGroupBox); - mainLayout->addWidget(notificationsGroupBox); - mainLayout->addWidget(animationGroupBox); - mainLayout->addWidget(deckEditorGroupBox); - mainLayout->addWidget(replayGroupBox); - mainLayout->addStretch(); - - setLayout(mainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &UserInterfaceSettingsPage::retranslateUi); - retranslateUi(); -} - -void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) -{ - specNotificationsEnabledCheckBox.setEnabled(i != 0); - buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0); - if (i == 0) { - specNotificationsEnabledCheckBox.setChecked(false); - buddyConnectNotificationsEnabledCheckBox.setChecked(false); - } -} - -void UserInterfaceSettingsPage::retranslateUi() -{ - generalGroupBox->setTitle(tr("General interface settings")); - doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); - clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); - playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); - doNotDeleteArrowsInSubPhasesCheckBox.setText(tr("Do not delete &arrows inside of subphases")); - closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); - focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); - annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); - showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); - showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); - useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); - notificationsGroupBox->setTitle(tr("Notifications settings")); - notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); - specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating")); - buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect")); - animationGroupBox->setTitle(tr("Animation settings")); - tapAnimationCheckBox.setText(tr("&Tap/untap animation")); - deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); - openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); - visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby")); - visualDeckStorageSelectionAnimationCheckBox.setText(tr("Use selection animation for Visual Deck Storage")); - visualDeckStoragePromptForConversionLabel.setText( - tr("When adding a tag in the visual deck storage to a .txt deck:")); - visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexNone, - tr("do nothing")); - visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexPrompt, - tr("ask to convert to .cod")); - visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexAlways, - tr("always convert to .cod")); - defaultDeckEditorTypeLabel.setText(tr("Default deck editor type")); - defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor")); - defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor")); - replayGroupBox->setTitle(tr("Replay settings")); - rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); - rewindBufferingMsBox.setSuffix(" ms"); -} - -DeckEditorSettingsPage::DeckEditorSettingsPage() -{ - picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload()); - connect(&picDownloadCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setPicDownload); - - urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); - urlLinkLabel.setOpenExternalLinks(true); - - connect(&clearDownloadedPicsButton, &QPushButton::clicked, this, - &DeckEditorSettingsPage::clearDownloadedPicsButtonClicked); - connect(&resetDownloadURLs, &QPushButton::clicked, this, &DeckEditorSettingsPage::resetDownloadedURLsButtonClicked); - - auto *lpGeneralGrid = new QGridLayout; - auto *lpSpoilerGrid = new QGridLayout; - - mcDownloadSpoilersCheckBox.setChecked(SettingsCache::instance().getDownloadSpoilersStatus()); - - mpSpoilerSavePathLineEdit = new QLineEdit(SettingsCache::instance().getSpoilerCardDatabasePath()); - mpSpoilerSavePathLineEdit->setReadOnly(true); - mpSpoilerPathButton = new QPushButton("..."); - connect(mpSpoilerPathButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::spoilerPathButtonClicked); - - updateNowButton = new QPushButton; - updateNowButton->setFixedWidth(150); - connect(updateNowButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::updateSpoilers); - - // Update the GUI depending on if the box is ticked or not - setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); - - urlList = new QListWidget; - urlList->setSelectionMode(QAbstractItemView::SingleSelection); - urlList->setAlternatingRowColors(true); - urlList->setDragEnabled(true); - urlList->setDragDropMode(QAbstractItemView::InternalMove); - connect(urlList->model(), &QAbstractItemModel::rowsMoved, this, &DeckEditorSettingsPage::urlListChanged); - - urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); - - aAdd = new QAction(this); - aAdd->setIcon(QPixmap("theme:icons/increment")); - connect(aAdd, &QAction::triggered, this, &DeckEditorSettingsPage::actAddURL); - - aEdit = new QAction(this); - aEdit->setIcon(QPixmap("theme:icons/pencil")); - connect(aEdit, &QAction::triggered, this, &DeckEditorSettingsPage::actEditURL); - - aRemove = new QAction(this); - aRemove->setIcon(QPixmap("theme:icons/decrement")); - connect(aRemove, &QAction::triggered, this, &DeckEditorSettingsPage::actRemoveURL); - - auto *urlToolBar = new QToolBar; - urlToolBar->setOrientation(Qt::Vertical); - urlToolBar->addAction(aAdd); - urlToolBar->addAction(aRemove); - urlToolBar->addAction(aEdit); - urlToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - - auto *urlListLayout = new QHBoxLayout; - urlListLayout->addWidget(urlToolBar); - urlListLayout->addWidget(urlList); - - // pixmap cache - pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); - // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size) - pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX); - pixmapCacheEdit.setSingleStep(64); - pixmapCacheEdit.setValue(SettingsCache::instance().getPixmapCacheSize()); - pixmapCacheEdit.setSuffix(" MB"); - - networkCacheEdit.setMinimum(NETWORK_CACHE_SIZE_MIN); - networkCacheEdit.setMaximum(NETWORK_CACHE_SIZE_MAX); - networkCacheEdit.setSingleStep(1); - networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB()); - networkCacheEdit.setSuffix(" MB"); - - networkRedirectCacheTtlEdit.setMinimum(NETWORK_REDIRECT_CACHE_TTL_MIN); - networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX); - networkRedirectCacheTtlEdit.setSingleStep(1); - networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl()); - - auto networkCacheLayout = new QHBoxLayout; - networkCacheLayout->addStretch(); - networkCacheLayout->addWidget(&networkCacheLabel); - networkCacheLayout->addWidget(&networkCacheEdit); - - auto networkRedirectCacheLayout = new QHBoxLayout; - networkRedirectCacheLayout->addStretch(); - networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel); - networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit); - - auto pixmapCacheLayout = new QHBoxLayout; - pixmapCacheLayout->addStretch(); - pixmapCacheLayout->addWidget(&pixmapCacheLabel); - pixmapCacheLayout->addWidget(&pixmapCacheEdit); - - // Top Layout - lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); - lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); - lpGeneralGrid->addLayout(urlListLayout, 1, 0, 1, 2); - lpGeneralGrid->addLayout(networkCacheLayout, 2, 1); - lpGeneralGrid->addLayout(networkRedirectCacheLayout, 3, 0); - lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 1); - lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0); - lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1); - - // Spoiler Layout - lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); - lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0); - lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1); - lpSpoilerGrid->addWidget(mpSpoilerPathButton, 1, 2); - lpSpoilerGrid->addWidget(&lastUpdatedLabel, 2, 0); - lpSpoilerGrid->addWidget(updateNowButton, 2, 1); - lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop); - - // On a change to the checkbox, hide/un-hide the other fields - connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, &SettingsCache::instance(), - &SettingsCache::setDownloadSpoilerStatus); - connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, this, &DeckEditorSettingsPage::setSpoilersEnabled); - connect(&pixmapCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), - &SettingsCache::setPixmapCacheSize); - connect(&networkCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), - &SettingsCache::setNetworkCacheSizeInMB); - connect(&networkRedirectCacheTtlEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), - &SettingsCache::setNetworkRedirectCacheTtl); - - mpGeneralGroupBox = new QGroupBox; - mpGeneralGroupBox->setLayout(lpGeneralGrid); - - mpSpoilerGroupBox = new QGroupBox; - mpSpoilerGroupBox->setLayout(lpSpoilerGrid); - - auto *lpMainLayout = new QVBoxLayout; - lpMainLayout->addWidget(mpGeneralGroupBox); - lpMainLayout->addWidget(mpSpoilerGroupBox); - - setLayout(lpMainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DeckEditorSettingsPage::retranslateUi); - retranslateUi(); -} - -void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() -{ - SettingsCache::instance().downloads().resetToDefaultURLs(); - urlList->clear(); - urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); - QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); -} - -void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked() -{ - CardPictureLoader::clearNetworkCache(); - - // These are not used anymore, but we don't delete them automatically, so - // we should do it here lest we leave pictures hanging around on users' - // machines. - QString picsPath = SettingsCache::instance().getPicsPath() + "/downloadedPics/"; - QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - bool outerSuccessRemove = true; - for (const auto &dir : dirs) { - QString currentPath = picsPath + dir + "/"; - QStringList files = QDir(currentPath).entryList(QDir::Files); - bool innerSuccessRemove = true; - for (int j = 0; j < files.length(); j++) { - if (!QDir(currentPath).remove(files.at(j))) { - qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); - outerSuccessRemove = false; - innerSuccessRemove = false; - } - qInfo() << "Removed " << currentPath << files.at(j); - } - - if (innerSuccessRemove) { - bool success = QDir(picsPath).rmdir(dir); - if (!success) { - qInfo() << "Failed to remove inner directory" << picsPath; - } else { - qInfo() << "Removed" << currentPath; - } - } - } - if (outerSuccessRemove) { - QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); - QDir(SettingsCache::instance().getPicsPath()).rmdir("downloadedPics"); - } else { - QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); - } -} - -void DeckEditorSettingsPage::actAddURL() -{ - bool ok; - QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok); - if (ok) { - urlList->addItem(msg); - storeSettings(); - } -} - -void DeckEditorSettingsPage::actRemoveURL() -{ - if (urlList->currentItem() != nullptr) { - delete urlList->takeItem(urlList->currentRow()); - storeSettings(); - } -} - -void DeckEditorSettingsPage::actEditURL() -{ - if (urlList->currentItem()) { - QString oldText = urlList->currentItem()->text(); - bool ok; - QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok); - if (ok) { - urlList->currentItem()->setText(msg); - storeSettings(); - } - } -} - -void DeckEditorSettingsPage::storeSettings() -{ - qInfo() << "URL Priority Reset"; - - QStringList downloadUrls; - for (int i = 0; i < urlList->count(); i++) { - qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); - downloadUrls << urlList->item(i)->text(); - } - SettingsCache::instance().downloads().setDownloadUrls(downloadUrls); -} - -void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) -{ - storeSettings(); -} - -void DeckEditorSettingsPage::updateSpoilers() -{ - // Disable the button so the user can only press it once at a time - updateNowButton->setDisabled(true); - updateNowButton->setText(tr("Updating...")); - - // Create a new SBU that will act as if the client was just reloaded - auto *sbu = new SpoilerBackgroundUpdater(); - connect(sbu, &SpoilerBackgroundUpdater::spoilerCheckerDone, this, &DeckEditorSettingsPage::unlockSettings); - connect(sbu, &SpoilerBackgroundUpdater::spoilersUpdatedSuccessfully, this, &DeckEditorSettingsPage::unlockSettings); -} - -void DeckEditorSettingsPage::unlockSettings() -{ - updateNowButton->setDisabled(false); - updateNowButton->setText(tr("Update Spoilers")); -} - -QString DeckEditorSettingsPage::getLastUpdateTime() -{ - QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath(); - QFileInfo fi(fileName); - QDir fileDir(fi.path()); - QFile file(fileName); - - if (file.exists()) { - return fi.lastModified().toString("MMM d, hh:mm"); - } - - return QString(); -} - -void DeckEditorSettingsPage::spoilerPathButtonClicked() -{ - QString lsPath = QFileDialog::getExistingDirectory(this, tr("Choose path"), mpSpoilerSavePathLineEdit->text()); - if (lsPath.isEmpty()) { - return; - } - - mpSpoilerSavePathLineEdit->setText(lsPath + "/spoiler.xml"); - SettingsCache::instance().setSpoilerDatabasePath(lsPath + "/spoiler.xml"); -} - -void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput) -{ - msDownloadSpoilersLabel.setEnabled(anInput); - mcSpoilerSaveLabel.setEnabled(anInput); - mpSpoilerSavePathLineEdit->setEnabled(anInput); - mpSpoilerPathButton->setEnabled(anInput); - lastUpdatedLabel.setEnabled(anInput); - updateNowButton->setEnabled(anInput); - infoOnSpoilersLabel.setEnabled(anInput); - - if (!anInput) { - SpoilerBackgroundUpdater::deleteSpoilerFile(); - } -} - -void DeckEditorSettingsPage::retranslateUi() -{ - mpGeneralGroupBox->setTitle(tr("URL Download Priority")); - mpSpoilerGroupBox->setTitle(tr("Spoilers")); - mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically")); - mcSpoilerSaveLabel.setText(tr("Spoiler Location:")); - lastUpdatedLabel.setText(tr("Last Change") + ": " + getLastUpdateTime()); - infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" + - tr("Press the button to manually update without relaunching") + "\n\n" + - tr("Do not close settings until manual update is complete")); - picDownloadCheckBox.setText(tr("Download card pictures on the fly")); - urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); - clearDownloadedPicsButton.setText(tr("Delete Downloaded Images")); - resetDownloadURLs.setText(tr("Reset Download URLs")); - networkCacheLabel.setText(tr("Network Cache Size:")); - networkCacheEdit.setToolTip(tr("On-disk cache for downloaded pictures")); - networkRedirectCacheTtlLabel.setText(tr("Redirect Cache TTL:")); - networkRedirectCacheTtlEdit.setToolTip(tr("How long cached redirects for urls are valid for.")); - pixmapCacheLabel.setText(tr("Picture Cache Size:")); - pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen")); - updateNowButton->setText(tr("Update Spoilers")); - aAdd->setText(tr("Add New URL")); - aEdit->setText(tr("Edit URL")); - aRemove->setText(tr("Remove URL")); - networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)")); -} - -MessagesSettingsPage::MessagesSettingsPage() -{ - chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention()); - connect(&chatMentionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setChatMention); - - chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter()); - connect(&chatMentionCompleterCheckbox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setChatMentionCompleter); - - explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); - explainMessagesLabel.setOpenExternalLinks(true); - - ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers()); - ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages()); - connect(&ignoreUnregUsersMainChat, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setIgnoreUnregisteredUsers); - connect(&ignoreUnregUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setIgnoreUnregisteredUserMessages); - - invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground()); - connect(&invertMentionForeground, &QCheckBox::QT_STATE_CHANGED, this, &MessagesSettingsPage::updateTextColor); - - invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground()); - connect(&invertHighlightForeground, &QCheckBox::QT_STATE_CHANGED, this, - &MessagesSettingsPage::updateTextHighlightColor); - - mentionColor = new QLineEdit(); - mentionColor->setText(SettingsCache::instance().getChatMentionColor()); - updateMentionPreview(); - connect(mentionColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateColor); - - messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); - connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowMessagePopups); - - mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup()); - connect(&mentionPopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowMentionPopups); - - roomHistory.setChecked(SettingsCache::instance().getRoomHistory()); - connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setRoomHistory); - - customAlertString = new QLineEdit(); - customAlertString->setText(SettingsCache::instance().getHighlightWords()); - connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance(), &SettingsCache::setHighlightWords); - - auto *chatGrid = new QGridLayout; - chatGrid->addWidget(&chatMentionCheckBox, 0, 0); - chatGrid->addWidget(&invertMentionForeground, 0, 1); - chatGrid->addWidget(mentionColor, 0, 2); - chatGrid->addWidget(&chatMentionCompleterCheckbox, 1, 0); - chatGrid->addWidget(&ignoreUnregUsersMainChat, 2, 0); - chatGrid->addWidget(&hexLabel, 1, 2); - chatGrid->addWidget(&ignoreUnregUserMessages, 3, 0); - chatGrid->addWidget(&messagePopups, 4, 0); - chatGrid->addWidget(&mentionPopups, 5, 0); - chatGrid->addWidget(&roomHistory, 6, 0); - chatGroupBox = new QGroupBox; - chatGroupBox->setLayout(chatGrid); - - highlightColor = new QLineEdit(); - highlightColor->setText(SettingsCache::instance().getChatHighlightColor()); - updateHighlightPreview(); - connect(highlightColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateHighlightColor); - - auto *highlightNotice = new QGridLayout; - highlightNotice->addWidget(highlightColor, 0, 2); - highlightNotice->addWidget(&invertHighlightForeground, 0, 1); - highlightNotice->addWidget(&hexHighlightLabel, 1, 2); - highlightNotice->addWidget(customAlertString, 0, 0); - highlightNotice->addWidget(&customAlertStringLabel, 1, 0); - highlightGroupBox = new QGroupBox; - highlightGroupBox->setLayout(highlightNotice); - - messageList = new QListWidget; - - int count = SettingsCache::instance().messages().getCount(); - for (int i = 0; i < count; i++) - messageList->addItem(SettingsCache::instance().messages().getMessageAt(i)); - - aAdd = new QAction(this); - aAdd->setIcon(QPixmap("theme:icons/increment")); - connect(aAdd, &QAction::triggered, this, &MessagesSettingsPage::actAdd); - - aEdit = new QAction(this); - aEdit->setIcon(QPixmap("theme:icons/pencil")); - connect(aEdit, &QAction::triggered, this, &MessagesSettingsPage::actEdit); - - aRemove = new QAction(this); - aRemove->setIcon(QPixmap("theme:icons/decrement")); - connect(aRemove, &QAction::triggered, this, &MessagesSettingsPage::actRemove); - - auto *messageToolBar = new QToolBar; - messageToolBar->setOrientation(Qt::Vertical); - messageToolBar->addAction(aAdd); - messageToolBar->addAction(aRemove); - messageToolBar->addAction(aEdit); - messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - - auto *messageListLayout = new QHBoxLayout; - messageListLayout->addWidget(messageToolBar); - messageListLayout->addWidget(messageList); - - auto *messagesLayout = new QVBoxLayout; // combines the explainer label with the actual messages widget pieces - messagesLayout->addLayout(messageListLayout); - messagesLayout->addWidget(&explainMessagesLabel); - - messageGroupBox = new QGroupBox; // draws a box around the above layout and allows it to be titled - messageGroupBox->setLayout(messagesLayout); - - auto *mainLayout = new QVBoxLayout; // combines the messages groupbox with the rest of the menu - mainLayout->addWidget(messageGroupBox); - mainLayout->addWidget(chatGroupBox); - mainLayout->addWidget(highlightGroupBox); - - setLayout(mainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &MessagesSettingsPage::retranslateUi); - retranslateUi(); -} - -void MessagesSettingsPage::updateColor(const QString &value) -{ -#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) - QColor colorToSet = QColor::fromString("#" + value); -#else - QColor colorToSet; - colorToSet.setNamedColor("#" + value); -#endif - if (colorToSet.isValid()) { - SettingsCache::instance().setChatMentionColor(value); - updateMentionPreview(); - } -} - -void MessagesSettingsPage::updateHighlightColor(const QString &value) -{ -#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) - QColor colorToSet = QColor::fromString("#" + value); -#else - QColor colorToSet; - colorToSet.setNamedColor("#" + value); -#endif - if (colorToSet.isValid()) { - SettingsCache::instance().setChatHighlightColor(value); - updateHighlightPreview(); - } -} - -void MessagesSettingsPage::updateTextColor(QT_STATE_CHANGED_T value) -{ - SettingsCache::instance().setChatMentionForeground(value); - updateMentionPreview(); -} - -void MessagesSettingsPage::updateTextHighlightColor(QT_STATE_CHANGED_T value) -{ - SettingsCache::instance().setChatHighlightForeground(value); - updateHighlightPreview(); -} - -void MessagesSettingsPage::updateMentionPreview() -{ - mentionColor->setStyleSheet( - "QLineEdit{background:#" + SettingsCache::instance().getChatMentionColor() + - ";color: " + (SettingsCache::instance().getChatMentionForeground() ? "white" : "black") + ";}"); -} - -void MessagesSettingsPage::updateHighlightPreview() -{ - highlightColor->setStyleSheet( - "QLineEdit{background:#" + SettingsCache::instance().getChatHighlightColor() + - ";color: " + (SettingsCache::instance().getChatHighlightForeground() ? "white" : "black") + ";}"); -} - -void MessagesSettingsPage::storeSettings() -{ - SettingsCache::instance().messages().setCount(messageList->count()); - for (int i = 0; i < messageList->count(); i++) - SettingsCache::instance().messages().setMessageAt(i, messageList->item(i)->text()); - emit SettingsCache::instance().messages().messageMacrosChanged(); -} - -void MessagesSettingsPage::actAdd() -{ - bool ok; - QString msg = - getTextWithMax(this, tr("Add message"), tr("Message:"), QLineEdit::Normal, QString(), &ok, MAX_TEXT_LENGTH); - if (ok) { - messageList->addItem(msg); - storeSettings(); - } -} - -void MessagesSettingsPage::actEdit() -{ - if (messageList->currentItem()) { - QString oldText = messageList->currentItem()->text(); - bool ok; - QString msg = - getTextWithMax(this, tr("Edit message"), tr("Message:"), QLineEdit::Normal, oldText, &ok, MAX_TEXT_LENGTH); - if (ok) { - messageList->currentItem()->setText(msg); - storeSettings(); - } - } -} - -void MessagesSettingsPage::actRemove() -{ - if (messageList->currentItem() != nullptr) { - delete messageList->takeItem(messageList->currentRow()); - storeSettings(); - } -} - -void MessagesSettingsPage::retranslateUi() -{ - chatGroupBox->setTitle(tr("Chat settings")); - highlightGroupBox->setTitle(tr("Custom alert words")); - chatMentionCheckBox.setText(tr("Enable chat mentions")); - chatMentionCompleterCheckbox.setText(tr("Enable mention completer")); - messageGroupBox->setTitle(tr("In-game message macros")); - explainMessagesLabel.setText( - QString("%2").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to use in-game message macros"))); - ignoreUnregUsersMainChat.setText(tr("Ignore chat room messages sent by unregistered users")); - ignoreUnregUserMessages.setText(tr("Ignore private messages sent by unregistered users")); - invertMentionForeground.setText(tr("Invert text color")); - invertHighlightForeground.setText(tr("Invert text color")); - messagePopups.setText(tr("Enable desktop notifications for private messages")); - mentionPopups.setText(tr("Enable desktop notification for mentions")); - roomHistory.setText(tr("Enable room message history on join")); - hexLabel.setText(tr("(Color is hexadecimal)")); - hexHighlightLabel.setText(tr("(Color is hexadecimal)")); - customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only")); - customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); - aAdd->setText(tr("Add New Message")); - aEdit->setText(tr("Edit Message")); - aRemove->setText(tr("Remove Message")); -} - -SoundSettingsPage::SoundSettingsPage() -{ - soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled()); - connect(&soundEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setSoundEnabled); - - QString themeName = SettingsCache::instance().getSoundThemeName(); - - QStringList themeDirs = soundEngine->getAvailableThemes().keys(); - for (int i = 0; i < themeDirs.size(); i++) { - themeBox.addItem(themeDirs[i]); - if (themeDirs[i] == themeName) - themeBox.setCurrentIndex(i); - } - - connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &SoundSettingsPage::themeBoxChanged); - connect(&soundTestButton, &QPushButton::clicked, soundEngine, &SoundEngine::testSound); - - masterVolumeSlider = new QSlider(Qt::Horizontal); - masterVolumeSlider->setMinimum(0); - masterVolumeSlider->setMaximum(100); - masterVolumeSlider->setValue(SettingsCache::instance().getMasterVolume()); - masterVolumeSlider->setToolTip(QString::number(SettingsCache::instance().getMasterVolume())); - connect(&SettingsCache::instance(), &SettingsCache::masterVolumeChanged, this, - &SoundSettingsPage::masterVolumeChanged); - connect(masterVolumeSlider, &QSlider::sliderReleased, soundEngine, &SoundEngine::testSound); - connect(masterVolumeSlider, &QSlider::valueChanged, &SettingsCache::instance(), &SettingsCache::setMasterVolume); - - masterVolumeSpinBox = new QSpinBox(); - masterVolumeSpinBox->setMinimum(0); - masterVolumeSpinBox->setMaximum(100); - masterVolumeSpinBox->setValue(SettingsCache::instance().getMasterVolume()); - connect(masterVolumeSlider, &QSlider::valueChanged, masterVolumeSpinBox, &QSpinBox::setValue); - connect(masterVolumeSpinBox, qOverload(&QSpinBox::valueChanged), masterVolumeSlider, &QSlider::setValue); - - auto *soundGrid = new QGridLayout; - soundGrid->addWidget(&soundEnabledCheckBox, 0, 0, 1, 3); - soundGrid->addWidget(&masterVolumeLabel, 1, 0); - soundGrid->addWidget(masterVolumeSlider, 1, 1); - soundGrid->addWidget(masterVolumeSpinBox, 1, 2); - soundGrid->addWidget(&themeLabel, 2, 0); - soundGrid->addWidget(&themeBox, 2, 1); - soundGrid->addWidget(&soundTestButton, 3, 1); - - soundGroupBox = new QGroupBox; - soundGroupBox->setLayout(soundGrid); - - auto *mainLayout = new QVBoxLayout; - mainLayout->addWidget(soundGroupBox); - mainLayout->addStretch(); - - setLayout(mainLayout); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &SoundSettingsPage::retranslateUi); - retranslateUi(); -} - -void SoundSettingsPage::themeBoxChanged(int index) -{ - QStringList themeDirs = soundEngine->getAvailableThemes().keys(); - if (index >= 0 && index < themeDirs.count()) - SettingsCache::instance().setSoundThemeName(themeDirs.at(index)); -} - -void SoundSettingsPage::masterVolumeChanged(int value) -{ - masterVolumeSlider->setToolTip(QString::number(value)); -} - -void SoundSettingsPage::retranslateUi() -{ - soundEnabledCheckBox.setText(tr("Enable &sounds")); - themeLabel.setText(tr("Current sounds theme:")); - soundTestButton.setText(tr("Test system sound engine")); - soundGroupBox->setTitle(tr("Sound settings")); - masterVolumeLabel.setText(tr("Master volume")); -} - -ShortcutSettingsPage::ShortcutSettingsPage() -{ - // search bar - searchEdit = new SearchLineEdit; - searchEdit->setObjectName("searchEdit"); - searchEdit->setClearButtonEnabled(true); - - setFocusProxy(searchEdit); - setFocusPolicy(Qt::ClickFocus); - - // table - shortcutsTable = new ShortcutTreeView(this); - - shortcutsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - shortcutsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - shortcutsTable->setColumnWidth(0, width() / 3 * 2); - searchEdit->setTreeView(shortcutsTable); - - connect(searchEdit, &SearchLineEdit::textChanged, shortcutsTable, &ShortcutTreeView::updateSearchString); - - // edit widget - currentActionGroupLabel = new QLabel(this); - currentActionGroupName = new QLabel(this); - currentActionLabel = new QLabel(this); - currentActionName = new QLabel(this); - currentShortcutLabel = new QLabel(this); - editTextBox = new SequenceEdit("", this); - shortcutsTable->installEventFilter(editTextBox); - - // buttons - faqLabel = new QLabel(this); - faqLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); - faqLabel->setOpenExternalLinks(true); - - btnResetAll = new QPushButton(this); - btnClearAll = new QPushButton(this); - - btnResetAll->setIcon(QPixmap("theme:icons/update")); - btnClearAll->setIcon(QPixmap("theme:icons/clearsearch")); - - // layout - auto *_editLayout = new QGridLayout; - _editLayout->addWidget(currentActionGroupLabel, 0, 0); - _editLayout->addWidget(currentActionGroupName, 0, 1); - _editLayout->addWidget(currentActionLabel, 1, 0); - _editLayout->addWidget(currentActionName, 1, 1); - _editLayout->addWidget(currentShortcutLabel, 2, 0); - _editLayout->addWidget(editTextBox, 2, 1); - - editShortcutGroupBox = new QGroupBox; - editShortcutGroupBox->setLayout(_editLayout); - - auto *_buttonsLayout = new QHBoxLayout; - _buttonsLayout->addWidget(faqLabel); - _buttonsLayout->addWidget(btnResetAll); - _buttonsLayout->addWidget(btnClearAll); - - auto *_mainLayout = new QVBoxLayout; - _mainLayout->addWidget(searchEdit); - _mainLayout->addWidget(shortcutsTable); - _mainLayout->addWidget(editShortcutGroupBox); - _mainLayout->addLayout(_buttonsLayout); - - setLayout(_mainLayout); - - connect(btnResetAll, &QPushButton::clicked, this, &ShortcutSettingsPage::resetShortcuts); - connect(btnClearAll, &QPushButton::clicked, this, &ShortcutSettingsPage::clearShortcuts); - - connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged); - - connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &ShortcutSettingsPage::retranslateUi); - retranslateUi(); -} - -void ShortcutSettingsPage::currentItemChanged(const QString &key) -{ - if (key.isEmpty()) { - currentActionGroupName->setText(""); - currentActionName->setText(""); - editTextBox->setShortcutName(""); - } else { - QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); - QString action = SettingsCache::instance().shortcuts().getShortcut(key).getName(); - currentActionGroupName->setText(group); - currentActionName->setText(action); - editTextBox->setShortcutName(key); - } -} - -void ShortcutSettingsPage::resetShortcuts() -{ - if (QMessageBox::question(this, tr("Restore all default shortcuts"), - tr("Do you really want to restore all default shortcuts?")) == QMessageBox::Yes) { - SettingsCache::instance().shortcuts().resetAllShortcuts(); - } -} - -void ShortcutSettingsPage::clearShortcuts() -{ - if (QMessageBox::question(this, tr("Clear all default shortcuts"), - tr("Do you really want to clear all shortcuts?")) == QMessageBox::Yes) { - SettingsCache::instance().shortcuts().clearAllShortcuts(); - } -} - -void ShortcutSettingsPage::retranslateUi() -{ - shortcutsTable->retranslateUi(); - - currentActionGroupLabel->setText(tr("Section:")); - currentActionLabel->setText(tr("Action:")); - currentShortcutLabel->setText(tr("Shortcut:")); - editTextBox->retranslateUi(); - faqLabel->setText(QString("%2").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to set custom shortcuts"))); - btnResetAll->setText(tr("Restore all default shortcuts")); - btnClearAll->setText(tr("Clear all shortcuts")); - searchEdit->setPlaceholderText(tr("Search by shortcut name")); -} +#include static QScrollArea *makeScrollable(QWidget *widget) { @@ -1789,6 +58,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage)); pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage)); pagesWidget->addWidget(new DeckEditorSettingsPage); + pagesWidget->addWidget(makeScrollable(new StorageSettingsPage)); pagesWidget->addWidget(new MessagesSettingsPage); pagesWidget->addWidget(new SoundSettingsPage); pagesWidget->addWidget(new ShortcutSettingsPage); @@ -1837,6 +107,11 @@ void DlgSettings::createIcons() deckEditorButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); deckEditorButton->setIcon(QPixmap("theme:config/deckeditor")); + storageButton = new QListWidgetItem(contentsWidget); + storageButton->setTextAlignment(Qt::AlignHCenter); + storageButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + storageButton->setIcon(QPixmap("theme:config/storage")); + messagesButton = new QListWidgetItem(contentsWidget); messagesButton->setTextAlignment(Qt::AlignHCenter); messagesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); @@ -1857,8 +132,9 @@ void DlgSettings::createIcons() void DlgSettings::changePage(QListWidgetItem *current, QListWidgetItem *previous) { - if (!current) + if (!current) { current = previous; + } pagesWidget->setCurrentIndex(contentsWidget->row(current)); } @@ -1930,7 +206,7 @@ void DlgSettings::closeEvent(QCloseEvent *event) } if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) { - //! \todo Prompt to create it + //! \todo Prompt to create the deck directory. if (QMessageBox::critical( this, tr("Error"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), @@ -1941,7 +217,7 @@ void DlgSettings::closeEvent(QCloseEvent *event) } if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) { - //! \todo Prompt to create it + //! \todo Prompt to create the pictures directory. if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back " "and set the correct path?"), @@ -1960,6 +236,7 @@ void DlgSettings::retranslateUi() generalButton->setText(tr("General")); appearanceButton->setText(tr("Appearance")); userInterfaceButton->setText(tr("User Interface")); + storageButton->setText(tr("Storage")); deckEditorButton->setText(tr("Card Sources")); messagesButton->setText(tr("Chat")); soundButton->setText(tr("Sound")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index b655a30bc..3ffee6388 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -1,343 +1,21 @@ /** * @file dlg_settings.h * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_SETTINGS_H #define DLG_SETTINGS_H -#include #include #include -#include -#include #include -#include -#include -#include inline Q_LOGGING_CATEGORY(DlgSettingsLog, "dlg_settings"); -class ShortcutTreeView; -class SearchLineEdit; -class QTreeView; -class QStandardItemModel; -class CardDatabase; -class QCloseEvent; -class QGridLayout; -class QHBoxLayout; -class QLineEdit; class QListWidget; -class QListWidgetItem; -class QRadioButton; -class QSlider; class QStackedWidget; -class QVBoxLayout; -class SequenceEdit; - -class AbstractSettingsPage : public QWidget -{ -public: - virtual void retranslateUi() = 0; -}; - -class GeneralSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -public: - GeneralSettingsPage(); - void retranslateUi() override; - -private slots: - void deckPathButtonClicked(); - void filtersPathButtonClicked(); - void replaysPathButtonClicked(); - void picsPathButtonClicked(); - void cardDatabasePathButtonClicked(); - void customCardDatabaseButtonClicked(); - void tokenDatabasePathButtonClicked(); - void resetAllPathsClicked(); - void languageBoxChanged(int index); - -private: - QStringList findQmFiles(); - QString languageName(const QString &lang); - QLineEdit *deckPathEdit; - QLineEdit *filtersPathEdit; - QLineEdit *replaysPathEdit; - QLineEdit *picsPathEdit; - QLineEdit *cardDatabasePathEdit; - QLineEdit *customCardDatabasePathEdit; - QLineEdit *tokenDatabasePathEdit; - QPushButton *resetAllPathsButton; - QLabel *allPathsResetLabel; - QGroupBox *personalGroupBox; - QGroupBox *pathsGroupBox; - QComboBox languageBox; - QCheckBox startupUpdateCheckCheckBox; - QLabel startupCardUpdateCheckBehaviorLabel; - QComboBox startupCardUpdateCheckBehaviorSelector; - QLabel cardUpdateCheckIntervalLabel; - QSpinBox cardUpdateCheckIntervalSpinBox; - QLabel lastCardUpdateCheckDateLabel; - QCheckBox updateNotificationCheckBox; - QCheckBox newVersionOracleCheckBox; - QComboBox updateReleaseChannelBox; - QLabel languageLabel; - QLabel deckPathLabel; - QLabel filtersPathLabel; - QLabel replaysPathLabel; - QLabel picsPathLabel; - QLabel cardDatabasePathLabel; - QLabel customCardDatabasePathLabel; - QLabel tokenDatabasePathLabel; - QLabel updateReleaseChannelLabel; - QLabel advertiseTranslationPageLabel; - QCheckBox showTipsOnStartup; -}; - -class AppearanceSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -private slots: - void themeBoxChanged(int index); - void openThemeLocation(); - void updateHomeTabSettingsVisibility(); - void showShortcutsChanged(QT_STATE_CHANGED_T enabled); - void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled); - - void cardViewInitialRowsMaxChanged(int value); - void cardViewExpandedRowsMaxChanged(int value); - -private: - QLabel themeLabel; - QComboBox themeBox; - QPushButton openThemeButton; - QLabel homeTabBackgroundSourceLabel; - QComboBox homeTabBackgroundSourceBox; - QLabel homeTabBackgroundShuffleFrequencyLabel; - QSpinBox homeTabBackgroundShuffleFrequencySpinBox; - QLabel homeTabDisplayCardNameLabel; - QCheckBox homeTabDisplayCardNameCheckBox; - QLabel minPlayersForMultiColumnLayoutLabel; - QLabel maxFontSizeForCardsLabel; - QCheckBox showShortcutsCheckBox; - QCheckBox showGameSelectorFilterToolbarCheckBox; - QCheckBox displayCardNamesCheckBox; - QCheckBox autoRotateSidewaysLayoutCardsCheckBox; - QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; - QCheckBox bumpSetsWithCardsInDeckToTopCheckBox; - QCheckBox cardScalingCheckBox; - QCheckBox roundCardCornersCheckBox; - QLabel verticalCardOverlapPercentLabel; - QSpinBox verticalCardOverlapPercentBox; - QLabel cardViewInitialRowsMaxLabel; - QSpinBox cardViewInitialRowsMaxBox; - QLabel cardViewExpandedRowsMaxLabel; - QSpinBox cardViewExpandedRowsMaxBox; - QCheckBox horizontalHandCheckBox; - QCheckBox leftJustifiedHandCheckBox; - QCheckBox invertVerticalCoordinateCheckBox; - QGroupBox *themeGroupBox; - QGroupBox *menuGroupBox; - QGroupBox *cardsGroupBox; - QGroupBox *handGroupBox; - QGroupBox *tableGroupBox; - QGroupBox *cardCountersGroupBox; - QList cardCounterNames; - QSpinBox minPlayersForMultiColumnLayoutEdit; - QSpinBox maxFontSizeForCardsEdit; - -public: - AppearanceSettingsPage(); - void retranslateUi() override; -}; - -class UserInterfaceSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -private slots: - void setNotificationEnabled(QT_STATE_CHANGED_T); - -private: - QCheckBox notificationsEnabledCheckBox; - QCheckBox specNotificationsEnabledCheckBox; - QCheckBox buddyConnectNotificationsEnabledCheckBox; - QCheckBox doubleClickToPlayCheckBox; - QCheckBox clickPlaysAllSelectedCheckBox; - QCheckBox playToStackCheckBox; - QCheckBox doNotDeleteArrowsInSubPhasesCheckBox; - QCheckBox closeEmptyCardViewCheckBox; - QCheckBox focusCardViewSearchBarCheckBox; - QCheckBox annotateTokensCheckBox; - QCheckBox showDragSelectionCountCheckBox; - QCheckBox showTotalSelectionCountCheckBox; - QCheckBox useTearOffMenusCheckBox; - QCheckBox tapAnimationCheckBox; - QCheckBox openDeckInNewTabCheckBox; - QLabel visualDeckStoragePromptForConversionLabel; - QComboBox visualDeckStoragePromptForConversionSelector; - QCheckBox visualDeckStorageInGameCheckBox; - QCheckBox visualDeckStorageSelectionAnimationCheckBox; - QLabel defaultDeckEditorTypeLabel; - QComboBox defaultDeckEditorTypeSelector; - QLabel rewindBufferingMsLabel; - QSpinBox rewindBufferingMsBox; - QGroupBox *generalGroupBox; - QGroupBox *notificationsGroupBox; - QGroupBox *animationGroupBox; - QGroupBox *deckEditorGroupBox; - QGroupBox *replayGroupBox; - -public: - UserInterfaceSettingsPage(); - void retranslateUi() override; -}; - -class DeckEditorSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -public: - DeckEditorSettingsPage(); - void retranslateUi() override; - QString getLastUpdateTime(); - -private slots: - void storeSettings(); - void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int); - void setSpoilersEnabled(bool); - void spoilerPathButtonClicked(); - void updateSpoilers(); - void unlockSettings(); - void actAddURL(); - void actRemoveURL(); - void actEditURL(); - void clearDownloadedPicsButtonClicked(); - void resetDownloadedURLsButtonClicked(); - -private: - QPushButton clearDownloadedPicsButton; - QPushButton resetDownloadURLs; - QLabel urlLinkLabel; - QCheckBox picDownloadCheckBox; - QListWidget *urlList; - QAction *aAdd, *aEdit, *aRemove; - QCheckBox mcDownloadSpoilersCheckBox; - QLabel msDownloadSpoilersLabel; - QGroupBox *mpGeneralGroupBox; - QGroupBox *mpSpoilerGroupBox; - QLineEdit *mpSpoilerSavePathLineEdit; - QLabel mcSpoilerSaveLabel; - QLabel lastUpdatedLabel; - QLabel infoOnSpoilersLabel; - QPushButton *mpSpoilerPathButton; - QPushButton *updateNowButton; - QLabel networkCacheLabel; - QSpinBox networkCacheEdit; - QLabel networkRedirectCacheTtlLabel; - QSpinBox networkRedirectCacheTtlEdit; - QSpinBox pixmapCacheEdit; - QLabel pixmapCacheLabel; -}; - -class MessagesSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -public: - MessagesSettingsPage(); - void retranslateUi() override; - -private slots: - void actAdd(); - void actEdit(); - void actRemove(); - void updateColor(const QString &value); - void updateHighlightColor(const QString &value); - void updateTextColor(QT_STATE_CHANGED_T value); - void updateTextHighlightColor(QT_STATE_CHANGED_T value); - -private: - QListWidget *messageList; - QAction *aAdd; - QAction *aEdit; - QAction *aRemove; - QCheckBox chatMentionCheckBox; - QCheckBox chatMentionCompleterCheckbox; - QCheckBox invertMentionForeground; - QCheckBox invertHighlightForeground; - QCheckBox ignoreUnregUsersMainChat; - QCheckBox ignoreUnregUserMessages; - QCheckBox messagePopups; - QCheckBox mentionPopups; - QCheckBox roomHistory; - QGroupBox *chatGroupBox; - QGroupBox *highlightGroupBox; - QGroupBox *messageGroupBox; - QLineEdit *mentionColor; - QLineEdit *highlightColor; - QLineEdit *customAlertString; - QLabel hexLabel; - QLabel hexHighlightLabel; - QLabel customAlertStringLabel; - QLabel explainMessagesLabel; - - void storeSettings(); - void updateMentionPreview(); - void updateHighlightPreview(); -}; - -class SoundSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -public: - SoundSettingsPage(); - void retranslateUi() override; - -private: - QLabel themeLabel; - QComboBox themeBox; - QGroupBox *soundGroupBox; - QPushButton soundTestButton; - QCheckBox soundEnabledCheckBox; - QLabel masterVolumeLabel; - QSlider *masterVolumeSlider; - QSpinBox *masterVolumeSpinBox; - -private slots: - void masterVolumeChanged(int value); - void themeBoxChanged(int index); -}; - -class ShortcutSettingsPage : public AbstractSettingsPage -{ - Q_OBJECT -public: - ShortcutSettingsPage(); - void retranslateUi() override; - -private: - SearchLineEdit *searchEdit; - ShortcutTreeView *shortcutsTable; - QVBoxLayout *mainLayout; - QHBoxLayout *buttonsLayout; - QGroupBox *editShortcutGroupBox; - QGridLayout *editLayout; - QLabel *currentActionGroupLabel; - QLabel *currentActionGroupName; - QLabel *currentActionLabel; - QLabel *currentActionName; - QLabel *currentShortcutLabel; - SequenceEdit *editTextBox; - QLabel *faqLabel; - QPushButton *btnResetAll; - QPushButton *btnClearAll; - -private slots: - void resetShortcuts(); - void clearShortcuts(); - void currentItemChanged(const QString &key); -}; +class QListWidgetItem; class DlgSettings : public QDialog { @@ -353,8 +31,8 @@ private slots: private: QListWidget *contentsWidget; QStackedWidget *pagesWidget; - QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *messagesButton, - *soundButton, *shortcutsButton; + QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *storageButton, + *messagesButton, *soundButton, *shortcutsButton; void createIcons(); void retranslateUi(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_startup_card_check.h b/cockatrice/src/interface/widgets/dialogs/dlg_startup_card_check.h index 44fc59d58..d26610983 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_startup_card_check.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_startup_card_check.h @@ -1,8 +1,8 @@ /** * @file dlg_startup_card_check.h * @ingroup CardDatabaseUpdateDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_STARTUP_CARD_CHECK_H #define DLG_STARTUP_CARD_CHECK_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h index 3dad7c652..e493481d0 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h @@ -1,8 +1,8 @@ /** * @file dlg_tip_of_the_day.h * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_TIPOFDAY_H #define DLG_TIPOFDAY_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index 0a9244dec..f12550fa8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -40,7 +40,7 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) buttonBox->addButton(ok, QDialogButtonBox::AcceptRole); connect(gotoDownload, &QPushButton::clicked, this, &DlgUpdate::gotoDownloadPage); - // TODO: make reinstall button actually do something when clicked + //! \todo Make reinstall button actually do something when clicked. // connect(manualDownload, &QPushButton::clicked, this, &DlgUpdate::downloadUpdate); connect(stopDownload, &QPushButton::clicked, this, &DlgUpdate::cancelDownload); connect(ok, &QPushButton::clicked, this, &DlgUpdate::closeDialog); @@ -154,8 +154,9 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas ")

" + tr("Do you want to update now?"), QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) + if (reply == QMessageBox::Yes) { downloadUpdate(release->getName()); + } } else { QMessageBox::information( this, tr("Update Available"), @@ -219,7 +220,8 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath) { setLabel(tr("Installing...")); // Try to open the installer. If it opens, quit Cockatrice - if (QDesktopServices::openUrl(filepath)) { + if (QProcess::startDetached( + QString("\"%1\" /R /D=\"%2\"").arg(filepath.toLocalFile(), QCoreApplication::applicationDirPath()))) { QMetaObject::invokeMethod(static_cast(parent()), "close", Qt::QueuedConnection); qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice"; close(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.h b/cockatrice/src/interface/widgets/dialogs/dlg_update.h index daf0b9e47..7bd0020d5 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.h @@ -1,8 +1,8 @@ /** * @file dlg_update.h * @ingroup ClientUpdateDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_UPDATE_H #define DLG_UPDATE_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp index 3dd0fedb3..4eb054647 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.cpp @@ -60,8 +60,9 @@ void DlgViewLog::actCopyToClipboard() void DlgViewLog::loadInitialLogBuffer() { QList logBuffer = Logger::getInstance().getLogBuffer(); - for (const QString &message : logBuffer) + for (const QString &message : logBuffer) { appendLogEntry(message); + } } void DlgViewLog::appendLogEntry(const QString &message) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.h b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.h index f0b900527..5c7315b50 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_view_log.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_view_log.h @@ -1,8 +1,8 @@ /** * @file dlg_view_log.h * @ingroup ServerLogDialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DLG_VIEWLOG_H #define DLG_VIEWLOG_H diff --git a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.cpp b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.cpp index 94996e975..66170792a 100644 --- a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.cpp +++ b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.cpp @@ -73,8 +73,9 @@ TipsOfTheDay::~TipsOfTheDay() QVariant TipsOfTheDay::data(const QModelIndex &index, int /*role*/) const { - if (!index.isValid() || index.row() >= tipList->size() || index.column() >= TIPDDBMODEL_COLUMNS) + if (!index.isValid() || index.row() >= tipList->size() || index.column() >= TIPDDBMODEL_COLUMNS) { return QVariant(); + } TipOfTheDay tip = tipList->at(index.row()); switch (index.column()) { diff --git a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h index d61fddab5..9cda07e2b 100644 --- a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h @@ -1,8 +1,8 @@ /** * @file tip_of_the_day.h * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TIP_OF_DAY_H #define TIP_OF_DAY_H diff --git a/cockatrice/src/interface/widgets/general/background_sources.h b/cockatrice/src/interface/widgets/general/background_sources.h index 15bf1d377..423111bb6 100644 --- a/cockatrice/src/interface/widgets/general/background_sources.h +++ b/cockatrice/src/interface/widgets/general/background_sources.h @@ -1,8 +1,8 @@ /** * @file background_sources.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_BACKGROUND_SOURCES_H #define COCKATRICE_BACKGROUND_SOURCES_H @@ -40,8 +40,9 @@ public: static QString toId(Type type) { for (const auto &e : all()) { - if (e.type == type) + if (e.type == type) { return e.id; + } } return {}; } @@ -49,8 +50,9 @@ public: static Type fromId(const QString &id) { for (const auto &e : all()) { - if (id == e.id) + if (id == e.id) { return e.type; + } } return Theme; // default } @@ -58,8 +60,9 @@ public: static QString toDisplay(Type type) { for (const auto &e : all()) { - if (e.type == type) + if (e.type == type) { return QObject::tr(e.trKey); + } } return {}; } diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.h b/cockatrice/src/interface/widgets/general/display/banner_widget.h index 8a81dcfce..77a05abb6 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.h +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.h @@ -3,8 +3,8 @@ * @ingroup Widgets * @ingroup DeckEditorCardGroupWidgets * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef BANNER_WIDGET_H #define BANNER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp index 998808307..d9e108e6a 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -52,8 +52,9 @@ void BarChartWidget::paintEvent(QPaintEvent *) int barAreaWidth = right - left; int barCount = bars.size(); - if (barCount == 0) + if (barCount == 0) { return; + } int spacing = 6; int barWidth = (barAreaWidth - (barCount - 1) * spacing) / barCount; @@ -91,8 +92,9 @@ void BarChartWidget::paintEvent(QPaintEvent *) for (int j = 0; j < bar.segments.size(); j++) { const auto &seg = bar.segments[j]; int segHeight = (seg.value * barAreaHeight / highest); - if (segHeight < 2 && seg.value > 0) + if (segHeight < 2 && seg.value > 0) { segHeight = 2; + } int topY = yCurrent - segHeight; @@ -189,8 +191,9 @@ void BarChartWidget::mouseMoveEvent(QMouseEvent *e) for (int i = 0; i < segments.size(); i++) { const auto &seg = segments[i]; int segHeight = (seg.value * barAreaHeight / highest); - if (segHeight < 2 && seg.value > 0) + if (segHeight < 2 && seg.value > 0) { segHeight = 2; + } int topY = yCurrent - segHeight; int bottomY = yCurrent; diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h index 67ad24995..05814f15b 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h @@ -2,8 +2,8 @@ * @file bar_widget.h * @ingroup Widgets * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef BAR_WIDGET_H #define BAR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp index ed91ba03d..12ab5bb3b 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp @@ -26,16 +26,19 @@ QSize ColorBar::minimumSizeHint() const void ColorBar::paintEvent(QPaintEvent *) { - if (colors.isEmpty()) + if (colors.isEmpty()) { return; + } int total = 0; - for (const auto &pair : colors) + for (const auto &pair : colors) { total += pair.second; + } // Prevent divide-by-zero - if (total == 0) + if (total == 0) { return; + } QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); @@ -63,8 +66,9 @@ void ColorBar::paintEvent(QPaintEvent *) int segmentWidth = int(ratio * w); // Ensure the segment width is at least 1 to avoid degenerate rectangles - if (segmentWidth < 1) + if (segmentWidth < 1) { segmentWidth = 1; + } QColor base = colorFromName(key); @@ -100,8 +104,9 @@ void ColorBar::leaveEvent(QEvent *) void ColorBar::mouseMoveEvent(QMouseEvent *event) { - if (!isHovered || colors.isEmpty()) + if (!isHovered || colors.isEmpty()) { return; + } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) int x = int(event->position().x()); @@ -112,18 +117,21 @@ void ColorBar::mouseMoveEvent(QMouseEvent *event) #endif QString text = tooltipForPosition(x); - if (!text.isEmpty()) + if (!text.isEmpty()) { QToolTip::showText(gp, text, this); + } } QString ColorBar::tooltipForPosition(int x) const { int total = 0; - for (const auto &pair : colors) + for (const auto &pair : colors) { total += pair.second; + } - if (total == 0) + if (total == 0) { return {}; + } int pos = 0; @@ -149,12 +157,14 @@ QColor ColorBar::colorFromName(const QString &name) const {"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)}, }; - if (map.contains(name)) + if (map.contains(name)) { return map[name]; + } QColor c(name); - if (!c.isValid()) + if (!c.isValid()) { c = Qt::gray; + } return c; } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h index 0ef68f578..100f95310 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h @@ -99,13 +99,13 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; private: - /// Map of color keys to counts used for rendering. + /** @brief Map of color keys to counts used for rendering. */ QList> colors; - /// True if the mouse is currently inside the widget. + /** @brief True if the mouse is currently inside the widget. */ bool isHovered = false; - /// Minimum ratio a segment must exceed to be drawn. + /** @brief Minimum ratio a segment must exceed to be drawn. */ double minRatioThreshold = 0.0; /** diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h index ff7d91363..9cd6039c8 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h @@ -2,8 +2,8 @@ * @file percent_bar_widget.h * @ingroup Widgets * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PERCENT_BAR_WIDGET_H #define PERCENT_BAR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp index e027aabdd..9bad32bda 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -44,8 +44,9 @@ void SegmentedBarWidget::paintEvent(QPaintEvent *) const auto &seg = segments[i]; int segHeight = total > 0 ? (seg.value * barHeight / total) : 0; - if (segHeight < 2) + if (segHeight < 2) { segHeight = 2; + } QRect r(barX, yCurrent - segHeight, barWidth, segHeight); bool isTop = (i == segments.size() - 1); @@ -110,8 +111,9 @@ int SegmentedBarWidget::segmentAt(int y) const int top = currentTop - segHeight; int bottom = currentTop; - if (y >= top && y <= bottom) + if (y >= top && y <= bottom) { return i; + } currentTop -= segHeight; } diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp index 84232b36f..b129fbe18 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp @@ -157,8 +157,9 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const QPointF v = pt - center; double distance = std::hypot(v.x(), v.y()); - if (distance > size / 2.0) + if (distance > size / 2.0) { return {}; + } double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI; if (angle < 0) { diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.h b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.h index ca3043d10..c9b159b02 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.h +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.h @@ -1,8 +1,8 @@ /** * @file dynamic_font_size_label.h * @ingroup Widgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DYNAMICFONTSIZELABEL_H #define DYNAMICFONTSIZELABEL_H diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.h b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.h index 19e67fd28..540abfe6b 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.h +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.h @@ -1,8 +1,8 @@ /** * @file dynamic_font_size_push_button.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DYNAMICFONTSIZEPUSHBUTTON_H #define DYNAMICFONTSIZEPUSHBUTTON_H diff --git a/cockatrice/src/interface/widgets/general/display/labeled_input.h b/cockatrice/src/interface/widgets/general/display/labeled_input.h index 8424e09f0..838dfb280 100644 --- a/cockatrice/src/interface/widgets/general/display/labeled_input.h +++ b/cockatrice/src/interface/widgets/general/display/labeled_input.h @@ -1,8 +1,8 @@ /** * @file labeled_input.h * @ingroup DeckEditorCardGroupWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LABELED_INPUT_H #define LABELED_INPUT_H diff --git a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h index b2344b7d0..69232691e 100644 --- a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h +++ b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h @@ -1,8 +1,8 @@ /** * @file shadow_background_label.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef STYLEDLABEL_H #define STYLEDLABEL_H diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.h b/cockatrice/src/interface/widgets/general/home_styled_button.h index a053993da..f8d97e9aa 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.h +++ b/cockatrice/src/interface/widgets/general/home_styled_button.h @@ -1,8 +1,8 @@ /** * @file home_styled_button.h * @ingroup Widgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef HOME_STYLED_BUTTON_H #define HOME_STYLED_BUTTON_H diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index ea20ef6a0..e1687f997 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../interface/widgets/tabs/tab_supervisor.h" +#include "../../theme_manager.h" #include "../../window_main.h" #include "background_sources.h" #include "home_styled_button.h" @@ -46,6 +47,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) &HomeWidget::onBackgroundShuffleFrequencyChanged); // Lambda is cleaner to read than overloading this connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); }); + connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, + &HomeWidget::initializeBackgroundFromSource); connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &HomeWidget::updateButtonsToBackgroundColor); } @@ -136,8 +139,9 @@ void HomeWidget::updateRandomCard() } break; } - if (!newCard) + if (!newCard) { return; + } connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties); backgroundSourceCard->setCard(newCard); @@ -256,7 +260,7 @@ void HomeWidget::updateConnectButton(const ClientStatus status) QPair HomeWidget::extractDominantColors(const QPixmap &pixmap) { - if (SettingsCache::instance().getThemeName() == "Default" && + if (themeManager->isBuiltInTheme() && SettingsCache::instance().getHomeTabBackgroundSource() == BackgroundSources::toId(BackgroundSources::Theme)) { return QPair(QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80)); } diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index b30bb5407..90d003aa7 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -2,8 +2,8 @@ * @file home_widget.h * @ingroup Core * @ingroup Widgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef HOME_WIDGET_H #define HOME_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp index 75ab56b34..025f457bd 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp @@ -1,42 +1,52 @@ /** * @file flow_widget.cpp - * @brief Implementation of the FlowWidget class for organizing widgets in a flow layout within a scrollable area. + * @brief Implementation of FlowWidget — a QWidget hosting a FlowLayout inside an + * optional QScrollArea. */ #include "flow_widget.h" #include #include +#include +#include #include -#include -#include /** - * @brief Constructs a FlowWidget with a scrollable layout. + * @brief Constructs a FlowWidget. * - * @param parent The parent widget of this FlowWidget. - * @param horizontalPolicy The horizontal scroll bar policy for the scroll area. - * @param verticalPolicy The vertical scroll bar policy for the scroll area. + * When both scroll policies are Qt::ScrollBarAlwaysOff the scroll area is + * omitted entirely and the container is placed directly in the main layout. + * + * @param parent Parent widget. + * @param _flowDirection Qt::Horizontal for row-wrapping, Qt::Vertical for column-wrapping. + * @param horizontalPolicy Horizontal scroll-bar policy. + * @param verticalPolicy Vertical scroll-bar policy. */ FlowWidget::FlowWidget(QWidget *parent, const Qt::Orientation _flowDirection, const Qt::ScrollBarPolicy horizontalPolicy, const Qt::ScrollBarPolicy verticalPolicy) - : QWidget(parent), flowDirection(_flowDirection) + : QWidget(parent), scrollArea(nullptr), flowDirection(_flowDirection) + { - // Main Widget and Layout - if (_flowDirection == Qt::Horizontal) { - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - setMinimumWidth(0); + // Top-level size policy + // Horizontal flow: expand horizontally, let height be determined by wrapping. + // Vertical flow: expand vertically, let width be determined by wrapping. + if (flowDirection == Qt::Horizontal) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } else { - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); - setMinimumHeight(0); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } + mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); setLayout(mainLayout); - if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) { - // Scroll Area, which should expand as much as possible, since it should be the only direct child widget. + const bool useScrollArea = (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff); + + // Scroll area (optional) + if (useScrollArea) { scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); scrollArea->setMinimumSize(0, 0); @@ -48,39 +58,28 @@ FlowWidget::FlowWidget(QWidget *parent, scrollArea = nullptr; } - // Flow Layout inside the scroll area - if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) { - container = new QWidget(this); - } else { - container = new QWidget(scrollArea); - } + // Container widget (holds the FlowLayout) + container = new QWidget(useScrollArea ? static_cast(scrollArea) : this); + + // The container should be willing to grow in both axes; its actual size is + // governed by the FlowLayout's sizeHint / heightForWidth, not by a fixed policy. + container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + container->setMinimumSize(0, 0); flowLayout = new FlowLayout(container, flowDirection); - container->setLayout(flowLayout); - // The container should expand as much as possible, trusting the scrollArea to constrain it. - if (_flowDirection == Qt::Horizontal) { - container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - container->setMinimumWidth(0); - } else { - container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - container->setMinimumHeight(0); - } - // Use the FlowLayout container directly if we disable the ScrollArea - if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) { - mainLayout->addWidget(container); - } else { + if (useScrollArea) { scrollArea->setWidget(container); mainLayout->addWidget(scrollArea); + } else { + mainLayout->addWidget(container); } } /** * @brief Adds a widget to the flow layout within the FlowWidget. * - * Adjusts the widget's size policy based on the scroll bar policies. - * * @param widget_to_add The widget to add to the flow layout. */ void FlowWidget::addWidget(QWidget *widget_to_add) const @@ -100,77 +99,75 @@ void FlowWidget::removeWidget(QWidget *widgetToRemove) const } /** - * @brief Clears all widgets from the flow layout. + * @brief Removes all widgets from the flow layout and deletes them. * - * Deletes each widget and layout item, and recreates the flow layout if it was removed. + * If the layout pointer has somehow been lost it is recreated before returning. */ void FlowWidget::clearLayout() { - if (flowLayout != nullptr) { + if (flowLayout) { QLayoutItem *item; - while ((item = flowLayout->takeAt(0)) != nullptr) { - item->widget()->deleteLater(); // Delete the widget - delete item; // Delete the layout item + while ((item = flowLayout->takeAt(0))) { + if (item->widget()) { + item->widget()->deleteLater(); + } + delete item; } } else { + // Defensive fallback: recreate the layout if it was deleted externally. flowLayout = new FlowLayout(container, flowDirection); container->setLayout(flowLayout); } } /** - * @brief Handles resize events for the FlowWidget. + * @brief Marks the flow layout as dirty so Qt recomputes item positions. * - * Triggers layout recalculation and adjusts the scroll area content size. - * - * @param event The resize event containing the new size information. + * We do NOT call adjustSize() or activate() here: + * - adjustSize() would freeze geometry by calling setFixedSize internally. + * - activate() called inside a resize event can cause synchronous re-entrancy. + * Qt automatically calls setGeometry on the layout after a resize, so simply + * invalidating is sufficient. */ void FlowWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); + qCDebug(FlowWidgetSizeLog) << "resizeEvent:" << event->size(); - qCDebug(FlowWidgetSizeLog) << event->size(); - - // Trigger the layout to recalculate - if (flowLayout != nullptr) { - flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation - flowLayout->activate(); // Recalculate the layout based on the new size - } - - // Ensure the scroll area and its content adjust correctly - if (scrollArea != nullptr && scrollArea->widget() != nullptr) { - qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size(); - scrollArea->widget()->adjustSize(); - } else { - container->adjustSize(); + if (flowLayout) { + flowLayout->invalidate(); } } +void FlowWidget::setSpacing(int hSpacing, int vSpacing) +{ + flowLayout->setHorizontalMargin(hSpacing); + flowLayout->setVerticalMargin(vSpacing); + flowLayout->invalidate(); +} + /** - * @brief Sets the minimum size for all widgets inside the FlowWidget to the maximum sizeHint of all of them. + * @brief Sets every child widget's minimum size to the largest sizeHint in the layout. + * + * Useful for toolbars or button bars where all items should be the same size. */ void FlowWidget::setMinimumSizeToMaxSizeHint() { - QSize maxSize(0, 0); // Initialize to a zero size + QSize maxSize(0, 0); // Iterate over all widgets in the flow layout to find the maximum sizeHint for (int i = 0; i < flowLayout->count(); ++i) { - if (QLayoutItem *item = flowLayout->itemAt(i)) { - if (QWidget *widget = item->widget()) { - // Update the max size based on the sizeHint of each widget - QSize widgetSizeHint = widget->sizeHint(); - maxSize.setWidth(qMax(maxSize.width(), widgetSizeHint.width())); - maxSize.setHeight(qMax(maxSize.height(), widgetSizeHint.height())); - } + QLayoutItem *item = flowLayout->itemAt(i); + if (item && item->widget()) { + maxSize = maxSize.expandedTo(item->widget()->sizeHint()); } } // Set the minimum size for all widgets to the max sizeHint for (int i = 0; i < flowLayout->count(); ++i) { - if (QLayoutItem *item = flowLayout->itemAt(i)) { - if (QWidget *widget = item->widget()) { - widget->setMinimumSize(maxSize); - } + QLayoutItem *item = flowLayout->itemAt(i); + if (item && item->widget()) { + item->widget()->setMinimumSize(maxSize); } } } diff --git a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h index d9fa49937..a232336d8 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h +++ b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h @@ -1,22 +1,24 @@ /** * @file flow_widget.h * @ingroup UI - * @brief TODO: Document this. + * @brief A QWidget that wraps a FlowLayout inside an optional QScrollArea. */ +//! \todo Document this file. #ifndef FLOW_WIDGET_H #define FLOW_WIDGET_H + #include "../../../layouts/flow_layout.h" #include #include +#include #include -#include inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg); inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg); -class FlowWidget final : public QWidget +class FlowWidget : public QWidget { Q_OBJECT @@ -25,17 +27,20 @@ public: Qt::Orientation orientation, Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy); + void addWidget(QWidget *widget_to_add) const; void insertWidgetAtIndex(QWidget *toInsert, int index); void removeWidget(QWidget *widgetToRemove) const; void clearLayout(); + [[nodiscard]] int count() const; [[nodiscard]] QLayoutItem *itemAt(int index) const; - QScrollArea *scrollArea; + QScrollArea *scrollArea; ///< Null when both scroll policies are AlwaysOff. public slots: void setMinimumSizeToMaxSizeHint(); + void setSpacing(int hSpacing, int vSpacing); protected: void resizeEvent(QResizeEvent *event) override; @@ -47,4 +52,4 @@ private: QWidget *container; }; -#endif // FLOW_WIDGET_H +#endif // FLOW_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.cpp b/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.cpp index 2fd6cd6b8..97068978d 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.cpp +++ b/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.cpp @@ -33,7 +33,7 @@ OverlapControlWidget::OverlapControlWidget(int overlapPercentage, layout->addWidget(overlap_percentage_input); layout->addWidget(overlap_direction); - // TODO probably connect this to the parent + //! \todo Probably connect this to the parent. // connect(card_size_slider, &QSlider::valueChanged, display, &CardPicture::setScaleFactor); } diff --git a/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.h b/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.h index b2c19a07f..104a97da9 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.h +++ b/cockatrice/src/interface/widgets/general/layout_containers/overlap_control_widget.h @@ -1,8 +1,8 @@ /** * @file overlap_control_widget.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef OVERLAP_CONTROL_WIDGET_H #define OVERLAP_CONTROL_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/layout_containers/overlap_widget.h b/cockatrice/src/interface/widgets/general/layout_containers/overlap_widget.h index 9ac6ac80e..62c620201 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/overlap_widget.h +++ b/cockatrice/src/interface/widgets/general/layout_containers/overlap_widget.h @@ -1,8 +1,8 @@ /** * @file overlap_widget.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef OVERLAP_WIDGET_H #define OVERLAP_WIDGET_H diff --git a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h index ac8f3b787..eff9257bb 100644 --- a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h +++ b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h @@ -1,8 +1,8 @@ /** * @file deck_editor_menu.h * @ingroup DeckEditors - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_EDITOR_MENU_H #define DECK_EDITOR_MENU_H diff --git a/cockatrice/src/interface/widgets/menus/tearoff_menu.h b/cockatrice/src/interface/widgets/menus/tearoff_menu.h index 3e6c47012..ef1dfbcb3 100644 --- a/cockatrice/src/interface/widgets/menus/tearoff_menu.h +++ b/cockatrice/src/interface/widgets/menus/tearoff_menu.h @@ -1,8 +1,8 @@ /** * @file tearoff_menu.h * @ingroup GameMenus - * @brief TODO: Document this. */ +//! \todo Document this file. #pragma once diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index 36bccbcc3..05e269174 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -8,7 +8,7 @@ * @brief Constructor for the AllZonesCardAmountWidget class. * * Initializes the widget with its layout and sets up the connections and necessary - * UI elements for managing card counts in both the mainboard and sideboard zones. + * UI elements for managing card counts in all the mainboard, tokensboard and sideboard zones. * * @param parent The parent widget. * @param deckStateManager Pointer to the DeckStateManager @@ -31,13 +31,28 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN); zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE); + zoneLabelTokensboard = new ShadowBackgroundLabel(this, tr("Tokens")); + buttonBoxTokensboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_TOKENS); layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); - layout->addSpacing(25); + layout->addSpacing(12); + layout->addWidget(zoneLabelTokensboard, 0, Qt::AlignHCenter | Qt::AlignBottom); + layout->addWidget(buttonBoxTokensboard, 0, Qt::AlignHCenter | Qt::AlignTop); + layout->addSpacing(13); layout->addWidget(zoneLabelSideboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(buttonBoxSideboard, 0, Qt::AlignHCenter | Qt::AlignTop); + // Show Tokens buttons for token cards, Mainboard/Sideboard for non-token cards + bool isToken = rootCard.getInfo().getIsToken(); + + zoneLabelMainboard->setVisible(!isToken); + buttonBoxMainboard->setVisible(!isToken); + zoneLabelTokensboard->setVisible(isToken); + buttonBoxTokensboard->setVisible(isToken); + zoneLabelSideboard->setVisible(!isToken); + buttonBoxSideboard->setVisible(!isToken); + connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); @@ -67,15 +82,17 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) zoneLabelFont.setPointSize(newFontSize); zoneLabelMainboard->setFont(zoneLabelFont); zoneLabelSideboard->setFont(zoneLabelFont); + zoneLabelTokensboard->setFont(zoneLabelFont); // Repaint the widget (if necessary) repaint(); } -void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount) +void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount) { buttonBoxMainboard->setAmount(mainboardAmount); buttonBoxSideboard->setAmount(sideboardAmount); + buttonBoxTokensboard->setAmount(tokensboardAmount); } /** @@ -99,11 +116,21 @@ int AllZonesCardAmountWidget::getSideboardAmount() } /** - * @brief Checks if the amount is at least one in either the mainboard or sideboard. + * @brief Gets the card count in the tokensboard zone. + * + * @return The number of cards in the tokensboard. + */ +int AllZonesCardAmountWidget::getTokensboardAmount() +{ + return buttonBoxTokensboard->getAmount(); +} + +/** + * @brief Checks if the amount is at least one in either the mainboard or sideboard or tokensboard. */ bool AllZonesCardAmountWidget::isNonZero() { - return getMainboardAmount() > 0 || getSideboardAmount() > 0; + return getMainboardAmount() > 0 || getSideboardAmount() > 0 || getTokensboardAmount() > 0; } /** diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index d158d257e..de4a984be 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -2,8 +2,8 @@ * @file all_zones_card_amount_widget.h * @ingroup CardExtraInfoWidgets * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ALL_ZONES_CARD_AMOUNT_WIDGET_H #define ALL_ZONES_CARD_AMOUNT_WIDGET_H @@ -23,6 +23,7 @@ public: const ExactCard &rootCard); int getMainboardAmount(); int getSideboardAmount(); + int getTokensboardAmount(); bool isNonZero(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -33,7 +34,7 @@ public: public slots: void adjustFontSize(int scalePercentage); - void setAmounts(int mainboardAmount, int sideboardAmount); + void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); private: QVBoxLayout *layout; @@ -42,6 +43,8 @@ private: CardAmountWidget *buttonBoxMainboard; QLabel *zoneLabelSideboard; CardAmountWidget *buttonBoxSideboard; + QLabel *zoneLabelTokensboard; + CardAmountWidget *buttonBoxTokensboard; }; #endif // ALL_ZONES_CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 25222f437..ff47e7b9c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -11,7 +11,7 @@ * @param parent The parent widget. * @param cardSizeSlider Pointer to the QSlider for adjusting font size. * @param rootCard The root card to manage within the widget. - * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). + * @param zoneName The zone name (e.g., DECK_ZONE_MAIN , DECK_ZONE_SIDE, or DECK_ZONE_TOKENS). */ CardAmountWidget::CardAmountWidget(QWidget *parent, DeckStateManager *deckStateManager, @@ -36,13 +36,16 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, incrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); decrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); - // Set up connections based on the zone (Mainboard or Sideboard) + // Set up connections based on the zone (Mainboard, Sideboard, or Tokensboard) if (zoneName == DECK_ZONE_MAIN) { connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingMainboard); connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingMainboard); } else if (zoneName == DECK_ZONE_SIDE) { connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingSideboard); connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingSideboard); + } else if (zoneName == DECK_ZONE_TOKENS) { + connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingTokensboard); + connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingTokensboard); } cardCountInZone = new QLabel(QString::number(amount), this); @@ -137,6 +140,19 @@ void CardAmountWidget::updateCardCount() layout->activate(); } +static QString zoneLogName(const QString &zone) +{ + if (zone == DECK_ZONE_MAIN) { + return "mainboard"; + } else if (zone == DECK_ZONE_SIDE) { + return "sideboard"; + } else if (zone == DECK_ZONE_TOKENS) { + return "tokens"; + } else { + return "unknown"; + } +} + static QModelIndex addAndReplacePrintings(DeckListModel *model, const QModelIndex &existing, const ExactCard &rootCard, @@ -161,9 +177,9 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model, } /** - * @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard). + * @brief Adds a printing of the card to the specified zone (Mainboard, Sideboard, or Tokensboard). * - * @param zone The zone to add the card to (DECK_ZONE_MAIN or DECK_ZONE_SIDE). + * @param zone The zone to add the card to (DECK_ZONE_MAIN, DECK_ZONE_SIDE, or DECK_ZONE_TOKENS). */ void CardAmountWidget::addPrinting(const QString &zone) { @@ -183,12 +199,13 @@ void CardAmountWidget::addPrinting(const QString &zone) } } + QString zoneName = zoneLogName(zone); QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7") .arg(1 + extraCopies) .arg(rootCard.getName()) .arg(rootCard.getPrinting().getSet()->getShortName()) .arg(rootCard.getPrinting().getProperty("num")) - .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(zoneName) .arg(rootCard.getPrinting().getUuid()) .arg(replacingProviderless ? " (replaced providerless printings)" : ""); @@ -218,6 +235,14 @@ void CardAmountWidget::addPrintingSideboard() addPrinting(DECK_ZONE_SIDE); } +/** + * @brief Adds a printing to the tokens zone. + */ +void CardAmountWidget::addPrintingTokensboard() +{ + addPrinting(DECK_ZONE_TOKENS); +} + /** * @brief Removes a printing from the mainboard zone. */ @@ -234,18 +259,27 @@ void CardAmountWidget::removePrintingSideboard() decrementCardHelper(DECK_ZONE_SIDE); } +/** + * @brief Removes a printing from the tokens zone. + */ +void CardAmountWidget::removePrintingTokensboard() +{ + decrementCardHelper(DECK_ZONE_TOKENS); +} + /** * @brief Helper function to decrement the card count for a given zone. * - * @param zone The zone from which to remove the card (DECK_ZONE_MAIN or DECK_ZONE_SIDE). + * @param zone The zone from which to remove the card (DECK_ZONE_MAIN, DECK_ZONE_SIDE, or DECK_ZONE_TOKENS). */ void CardAmountWidget::decrementCardHelper(const QString &zone) { + QString zoneName = zoneLogName(zone); QString reason = QString("Removed 1 copy of '%1 (%2) %3' from %4 [ProviderID: %5]") .arg(rootCard.getName()) .arg(rootCard.getPrinting().getSet()->getShortName()) .arg(rootCard.getPrinting().getProperty("num")) - .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(zoneName) .arg(rootCard.getPrinting().getUuid()); deckStateManager->modifyDeck(reason, [this, &zone](auto model) { diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 3051b1691..2780e3ad2 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -2,8 +2,8 @@ * @file card_amount_widget.h * @ingroup CardExtraInfoWidgets * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_AMOUNT_WIDGET_H #define CARD_AMOUNT_WIDGET_H @@ -60,8 +60,10 @@ private: private slots: void addPrintingMainboard(); void addPrintingSideboard(); + void addPrintingTokensboard(); void removePrintingMainboard(); void removePrintingSideboard(); + void removePrintingTokensboard(); void adjustFontSize(int scalePercentage); }; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 71b93b297..76a416587 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -105,23 +105,30 @@ void PrintingSelector::printingsInDeckChanged() } /** - * @return A map of uuid to amounts (main, side). + * @return A map of uuid to amounts (main, side, tokens). */ -static QMap> tallyUuidCounts(const DeckListModel *model, const QString &cardName) +static QMap tallyUuidCounts(const DeckListModel *model, const QString &cardName) { - QMap> map; + QMap map; auto mainNodes = model->getCardNodesForZone(DECK_ZONE_MAIN); for (auto &node : mainNodes) { if (node->getName() == cardName) { - map[node->getCardProviderId()].first += node->getNumber(); + map[node->getCardProviderId()].mainboard += node->getNumber(); } } auto sideNodes = model->getCardNodesForZone(DECK_ZONE_SIDE); for (auto &node : sideNodes) { if (node->getName() == cardName) { - map[node->getCardProviderId()].second += node->getNumber(); + map[node->getCardProviderId()].sideboard += node->getNumber(); + } + } + + auto tokensNodes = model->getCardNodesForZone(DECK_ZONE_TOKENS); + for (auto &node : tokensNodes) { + if (node->getName() == cardName) { + map[node->getCardProviderId()].tokensboard += node->getNumber(); } } diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index f7844504d..14d73f836 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -1,8 +1,8 @@ /** * @file printing_selector.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_H #define PRINTING_SELECTOR_H @@ -22,6 +22,13 @@ #define BATCH_SIZE 10 +struct ZoneCounts +{ + int mainboard = 0; + int sideboard = 0; + int tokensboard = 0; +}; + class DeckStateManager; class PrintingSelectorCardSearchWidget; class PrintingSelectorCardSelectionWidget; @@ -59,9 +66,9 @@ signals: /** * The amounts of the printings in the deck has changed - * @param uuidToAmounts Map of uuids to the amounts (maindeck, sideboard) in the deck + * @param uuidToAmounts Map of uuids to the amounts (maindeck, sideboard, tokensboard) in the deck */ - void cardAmountsChanged(const QMap> &uuidToAmounts); + void cardAmountsChanged(const QMap &uuidToAmounts); private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 7d0b4882f..edeba86d1 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -67,10 +67,10 @@ void PrintingSelectorCardDisplayWidget::clampSetNameToPicture() update(); } -void PrintingSelectorCardDisplayWidget::updateCardAmounts(const QMap> &uuidToAmounts) +void PrintingSelectorCardDisplayWidget::updateCardAmounts(const QMap &uuidToAmounts) { - auto [main, side] = uuidToAmounts.value(rootCard.getPrinting().getUuid()); - overlayWidget->updateCardAmounts(main, side); + auto counts = uuidToAmounts.value(rootCard.getPrinting().getUuid()); + overlayWidget->updateCardAmounts(counts.mainboard, counts.sideboard, counts.tokensboard); } void PrintingSelectorCardDisplayWidget::resizeEvent(QResizeEvent *event) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index ac5c7c05f..4de561f4f 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -1,8 +1,8 @@ /** * @file printing_selector_card_display_widget.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H #define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H @@ -27,7 +27,7 @@ public: public slots: void clampSetNameToPicture(); - void updateCardAmounts(const QMap> &uuidToAmounts); + void updateCardAmounts(const QMap &uuidToAmounts); void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp index 1508b5243..dd5f6dd7f 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -116,9 +116,11 @@ void PrintingSelectorCardOverlayWidget::enterEvent(QEvent *event) updateVisibility(); } -void PrintingSelectorCardOverlayWidget::updateCardAmounts(int mainboardAmount, int sideboardAmount) +void PrintingSelectorCardOverlayWidget::updateCardAmounts(int mainboardAmount, + int sideboardAmount, + int tokensboardAmount) { - allZonesCardAmountWidget->setAmounts(mainboardAmount, sideboardAmount); + allZonesCardAmountWidget->setAmounts(mainboardAmount, sideboardAmount, tokensboardAmount); updateVisibility(); } @@ -147,8 +149,9 @@ void PrintingSelectorCardOverlayWidget::updateVisibility() */ void PrintingSelectorCardOverlayWidget::updatePinBadgeVisibility() { - if (!pinBadge || !cardInfoPicture) + if (!pinBadge || !cardInfoPicture) { return; + } // Query the persisted preference override to decide whether this printing is pinned. const auto &preferredProviderId = @@ -172,8 +175,8 @@ void PrintingSelectorCardOverlayWidget::updatePinBadgeVisibility() /** * @brief Handles the mouse leave event when the cursor leaves the overlay widget area. * - * When the cursor leaves the widget, the card amount widget is hidden if both the mainboard and sideboard - * amounts are zero. + * When the cursor leaves the widget, the card amount widget is hidden if all of the mainboard, sideboard, and + * tokensboard amounts are zero. * * @param event The event triggered when the mouse leaves the widget. */ diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h index ae2307c45..52a43d220 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -1,8 +1,8 @@ /** * @file printing_selector_card_overlay_widget.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H #define PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H @@ -39,7 +39,7 @@ signals: void cardPreferenceChanged(); public slots: - void updateCardAmounts(int mainboardAmount, int sideboardAmount); + void updateCardAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); private slots: void updateVisibility(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_search_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_search_widget.h index 821addd01..3efabe13c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_search_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_search_widget.h @@ -1,8 +1,8 @@ /** * @file printing_selector_card_search_widget.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H #define PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h index ecd5c83e3..0aa56c9d8 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -1,8 +1,8 @@ /** * @file printing_selector_card_selection_widget.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_CARD_SELECTION_WIDGET_H #define PRINTING_SELECTOR_CARD_SELECTION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h index b5a00b81e..bb5b60cdc 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h @@ -1,8 +1,8 @@ /** * @file printing_selector_card_sorting_widget.h * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PRINTING_SELECTOR_CARD_SORTING_WIDGET_H #define PRINTING_SELECTOR_CARD_SORTING_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h index 220f57256..140f190df 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h @@ -2,8 +2,8 @@ * @file set_name_and_collectors_number_display_widget.h * @ingroup CardExtraInfoWidgets * @ingroup PrintingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H #define SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp index 81812104a..badc437ee 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp @@ -35,19 +35,33 @@ void SettingsButtonWidget::setButtonIcon(QPixmap iconMap) button->setIcon(iconMap); } -void SettingsButtonWidget::setButtonText(const QString &buttonText) +void SettingsButtonWidget::setButtonText(const QString &text) { - // 🔓 unlock size constraints + buttonText = text; + button->setMinimumSize(QSize(0, 0)); button->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); - button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - button->setText(buttonText); - + button->setText(text); button->setFixedHeight(32); button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - button->setMinimumWidth(button->sizeHint().width()); + button->setMinimumWidth(32); // icon-only fallback minimum +} + +void SettingsButtonWidget::setCompact(bool _compact) +{ + compact = _compact; + if (compact) { + button->setToolButtonStyle(Qt::ToolButtonIconOnly); + button->setFixedWidth(32); + } else { + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setText(buttonText); + button->setFixedWidth(QWIDGETSIZE_MAX); // release fixed width + button->setMinimumWidth(32); + button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } } void SettingsButtonWidget::togglePopup() diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h index 36f01ac38..5dcbe059a 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h @@ -2,8 +2,8 @@ * @file settings_button_widget.h * @ingroup Widgets * @ingroup Settings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SETTINGS_BUTTON_WIDGET_H #define SETTINGS_BUTTON_WIDGET_H @@ -23,6 +23,11 @@ public: void removeSettingsWidget(QWidget *toRemove) const; void setButtonIcon(QPixmap iconMap); void setButtonText(const QString &buttonText); + void setCompact(bool compact); + bool isCompact() const + { + return compact; + } protected: void mousePressEvent(QMouseEvent *event) override; @@ -34,6 +39,8 @@ private slots: private: QHBoxLayout *layout; QToolButton *button; + QString buttonText; + bool compact; public: SettingsPopupWidget *popup; diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.h b/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.h index e9605e473..01e86abfe 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.h +++ b/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.h @@ -2,8 +2,8 @@ * @file settings_popup_widget.h * @ingroup Widgets * @ingroup Settings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SETTINGS_POPUP_WIDGET_H #define SETTINGS_POPUP_WIDGET_H diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp index fac3576f2..a1330a82d 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -19,16 +19,19 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay) const int eventCount = replay->event_list_size(); for (int i = 0; i < eventCount; ++i) { int j = i + 1; - while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) + while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) { ++j; + } const int numberEventsThisSecond = j - i; - for (int k = 0; k < numberEventsThisSecond; ++k) + for (int k = 0; k < numberEventsThisSecond; ++k) { replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + (int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); + } - if (j < eventCount) + if (j < eventCount) { lastEventTimestamp = replay->event_list(j).seconds_elapsed(); + } i += numberEventsThisSecond - 1; } } @@ -95,8 +98,7 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay) void ReplayManager::replayNextEvent(EventProcessingOptions options) { - game->getGame()->getGameEventHandler()->processGameEventContainer( - replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options); + emit eventReplayed(replay->event_list(timelineWidget->getCurrentEvent()), options); } void ReplayManager::replayFinished() diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h index 9469adcd4..a3e0126c7 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.h +++ b/cockatrice/src/interface/widgets/replay/replay_manager.h @@ -2,8 +2,8 @@ * @file replay_manager.h * @ingroup Core * @ingroup Replay - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef REPLAY_MANAGER_H #define REPLAY_MANAGER_H @@ -27,6 +27,7 @@ public: signals: void requestChatAndPhaseReset(); + void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); private: // Replay related members diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp index 25ee58b67..46a3dff40 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp @@ -27,20 +27,23 @@ void ReplayTimelineWidget::setTimeline(const QList &_replayTimeline) for (int i : replayTimeline) { if (i > binEndTime) { histogram.append(binValue); - if (binValue > maxBinValue) + if (binValue > maxBinValue) { maxBinValue = binValue; + } while (i > binEndTime + BIN_LENGTH) { histogram.append(0); binEndTime += BIN_LENGTH; } binValue = 1; binEndTime += BIN_LENGTH; - } else + } else { ++binValue; + } } histogram.append(binValue); - if (!replayTimeline.isEmpty()) + if (!replayTimeline.isEmpty()) { maxTime = replayTimeline.last(); + } update(); } @@ -53,8 +56,9 @@ void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */) qreal binWidth = (qreal)width() / histogram.size(); QPainterPath path; path.moveTo(0, height() - 1); - for (int i = 0; i < histogram.size(); ++i) + for (int i = 0; i < histogram.size(); ++i) { path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue)); + } path.lineTo(width() - 1, height() - 1); path.lineTo(0, height() - 1); painter.fillPath(path, Qt::black); @@ -99,8 +103,12 @@ void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering) update(); } -/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind -/// is processed at the end. When false, the backwards skip will always cause an immediate rewind +/** + * @brief Handles a backwards skip in the replay timeline. + * + * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind + * is processed at the end. When false, the backwards skip will always cause an immediate rewind. + */ void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering) { if (doRewindBuffering) { @@ -142,11 +150,12 @@ void ReplayTimelineWidget::replayTimerTimeout() processNewEvents(NORMAL_PLAYBACK); - if (!(currentVisualTime % 1000)) + if (!(currentVisualTime % 1000)) { update(); + } } -/// Processes all unprocessed events up to the current time. +/** @brief Processes all unprocessed events up to the current time. */ void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) { currentProcessedTime = currentVisualTime; @@ -156,12 +165,14 @@ void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) // backwards skip => always skip reveal windows // forwards skip => skip reveal windows that don't happen within a big skip of the target - if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) + if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { options |= SKIP_REVEAL_WINDOW; + } // backwards skip => always skip tap animation - if (playbackMode == BACKWARD_SKIP) + if (playbackMode == BACKWARD_SKIP) { options |= SKIP_TAP_ANIMATION; + } emit processNextEvent(options); ++currentEvent; diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index 07a0c0b4c..418d0c7e0 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -1,8 +1,8 @@ /** * @file replay_timeline_widget.h * @ingroup Replay - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef REPLAY_TIMELINE_WIDGET #define REPLAY_TIMELINE_WIDGET diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index 731f30942..ccbbe9246 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -88,10 +88,11 @@ void ChatView::refreshBlockColors() for (QTextBlock block = doc->begin(); block.isValid(); block = block.next()) { QTextBlockFormat fmt = block.blockFormat(); - if (even) + if (even) { fmt.setBackground(palette().base()); - else + } else { fmt.setBackground(palette().window()); + } fmt.setForeground(palette().text()); @@ -121,10 +122,11 @@ QTextCursor ChatView::prepareBlock(bool same) cursor.insertHtml("
"); } else { QTextBlockFormat blockFormat; - if (evenNumber) + if (evenNumber) { blockFormat.setBackground(palette().base()); - else + } else { blockFormat.setBackground(palette().window()); + } evenNumber = !evenNumber; @@ -144,8 +146,9 @@ void ChatView::appendHtml(const QString &html) { bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); prepareBlock().insertHtml(html); - if (atBottom) + if (atBottom) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + } } void ChatView::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor) @@ -156,12 +159,14 @@ void ChatView::appendHtmlServerMessage(const QString &html, bool optionalIsBold, "" + QDateTime::currentDateTime().toString("[hh:mm:ss] ") + html + ""; - if (optionalIsBold) + if (optionalIsBold) { htmlText = "" + htmlText + ""; + } prepareBlock().insertHtml(htmlText); - if (atBottom) + if (atBottom) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + } } void ChatView::appendCardTag(QTextCursor &cursor, const QString &cardName) @@ -180,8 +185,9 @@ void ChatView::appendCardTag(QTextCursor &cursor, const QString &cardName) void ChatView::appendUrlTag(QTextCursor &cursor, QString url) { - if (!url.contains("://")) + if (!url.contains("://")) { url.prepend("https://"); + } QTextCharFormat oldFormat = cursor.charFormat(); QTextCharFormat anchorFormat = oldFormat; @@ -255,7 +261,8 @@ void ChatView::appendMessage(QString message, defaultFormat = QTextCharFormat(); if (!isUserMessage) { if (messageType == Event_RoomSay::ChatHistory) { - defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color + //! \todo Remove hardcoded color. + defaultFormat.setForeground(Qt::gray); defaultFormat.setFontWeight(QFont::Light); defaultFormat.setFontItalic(true); static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s"); @@ -278,7 +285,8 @@ void ChatView::appendMessage(QString message, message.remove(0, pos.relativePosition - 2); // do not remove semicolon } } else { - defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color + //! \todo Remove hardcoded color. + defaultFormat.setForeground(Qt::darkGreen); defaultFormat.setFontWeight(QFont::Bold); } } @@ -317,8 +325,9 @@ void ChatView::appendMessage(QString message, } } - if (atBottom) + if (atBottom) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + } } void ChatView::checkTag(QTextCursor &cursor, QString &message) @@ -327,10 +336,11 @@ void ChatView::checkTag(QTextCursor &cursor, QString &message) message = message.mid(6); int closeTagIndex = message.indexOf("[/card]"); QString cardName = message.left(closeTagIndex); - if (closeTagIndex == -1) + if (closeTagIndex == -1) { message.clear(); - else + } else { message = message.mid(closeTagIndex + 7); + } appendCardTag(cursor, cardName); return; @@ -340,10 +350,11 @@ void ChatView::checkTag(QTextCursor &cursor, QString &message) message = message.mid(2); int closeTagIndex = message.indexOf("]]"); QString cardName = message.left(closeTagIndex); - if (closeTagIndex == -1) + if (closeTagIndex == -1) { message.clear(); - else + } else { message = message.mid(closeTagIndex + 2); + } appendCardTag(cursor, cardName); return; @@ -353,10 +364,11 @@ void ChatView::checkTag(QTextCursor &cursor, QString &message) message = message.mid(5); int closeTagIndex = message.indexOf("[/url]"); QString url = message.left(closeTagIndex); - if (closeTagIndex == -1) + if (closeTagIndex == -1) { message.clear(); - else + } else { message = message.mid(closeTagIndex + 6); + } appendUrlTag(cursor, url); return; @@ -587,8 +599,9 @@ QTextFragment ChatView::getFragmentUnderMouse(const QPoint &pos) const QTextBlock::iterator it; for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment frag = it.fragment(); - if (frag.contains(cursor.position())) + if (frag.contains(cursor.position())) { return frag; + } } return QTextFragment(); } @@ -604,10 +617,11 @@ void ChatView::mouseMoveEvent(QMouseEvent *event) if (scheme == "card") { hoveredItemType = HoveredCard; emit cardNameHovered(hoveredContent); - } else if (scheme == "user") + } else if (scheme == "user") { hoveredItemType = HoveredUser; - else + } else { hoveredItemType = HoveredUrl; + } viewport()->setCursor(Qt::PointingHandCursor); } else { hoveredItemType = HoveredNothing; @@ -650,8 +664,9 @@ void ChatView::mousePressEvent(QMouseEvent *event) case Qt::LeftButton: { if (event->modifiers() == Qt::ControlModifier) { emit openMessageDialog(userName, true); - } else + } else { emit addMentionTag("@" + userName); + } break; } default: @@ -668,16 +683,18 @@ void ChatView::mousePressEvent(QMouseEvent *event) void ChatView::mouseReleaseEvent(QMouseEvent *event) { - if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton)) + if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton)) { emit deleteCardInfoPopup(QString("_")); + } QTextBrowser::mouseReleaseEvent(event); } void ChatView::openLink(const QUrl &link) { - if ((link.scheme() == "card") || (link.scheme() == "user")) + if ((link.scheme() == "card") || (link.scheme() == "user")) { return; + } QDesktopServices::openUrl(link); } diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index d1939fbea..c5ae2b81a 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -2,8 +2,8 @@ * @file chat_view.h * @ingroup NetworkingWidgets * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CHATVIEW_H #define CHATVIEW_H diff --git a/cockatrice/src/interface/widgets/server/game_filter_configs.cpp b/cockatrice/src/interface/widgets/server/game_filter_configs.cpp new file mode 100644 index 000000000..5a6282b75 --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_filter_configs.cpp @@ -0,0 +1,7 @@ +#include "game_filter_configs.h" + +bool GameFilterConfigs::isDefault() const +{ + static const GameFilterConfigs DEFAULT = {}; + return *this == DEFAULT; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/game_filter_configs.h b/cockatrice/src/interface/widgets/server/game_filter_configs.h new file mode 100644 index 000000000..0ece7e00c --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_filter_configs.h @@ -0,0 +1,42 @@ +#ifndef COCKATRICE_GAME_FILTER_CONFIGS_H +#define COCKATRICE_GAME_FILTER_CONFIGS_H + +#include + +/** + * @brief The possible game filter configs. + */ +struct GameFilterConfigs +{ + static constexpr int DEFAULT_MAX_PLAYERS_MIN = 1; + static constexpr int DEFAULT_MAX_PLAYERS_MAX = 99; + + bool hideBuddiesOnlyGames = false; + bool hideIgnoredUserGames = false; + bool hideFullGames = false; + bool hideGamesThatStarted = false; + bool hidePasswordProtectedGames = false; + bool hideNotBuddyCreatedGames = false; + bool hideOpenDecklistGames = false; + QString gameNameFilter = ""; + QStringList creatorNameFilters = {}; + QSet gameTypeFilter = {}; + int maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN; + int maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX; + QTime maxGameAge = {}; + bool showOnlyIfSpectatorsCanWatch = false; + bool showSpectatorPasswordProtected = false; + bool showOnlyIfSpectatorsCanChat = false; + bool showOnlyIfSpectatorsCanSeeHands = false; + + bool operator==(const GameFilterConfigs &) const = default; + + /** + * @brief Checks if this config has exactly the default values. + * + * @return Whether this config has the default values + */ + bool isDefault() const; +}; + +#endif // COCKATRICE_GAME_FILTER_CONFIGS_H diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index f14cc6d82..9a41ca6ce 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -37,8 +37,9 @@ GameSelector::GameSelector(AbstractClient *_client, connect(gameListView, &QTreeView::customContextMenuRequested, this, &GameSelector::customContextMenu); gameListModel = new GamesModel(_rooms, _gameTypes, this); + gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager()); + if (showFilters) { - gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager()); gameListProxyModel->setSourceModel(gameListModel); gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); gameListView->setModel(gameListProxyModel); @@ -60,14 +61,17 @@ GameSelector::GameSelector(AbstractClient *_client, gameListView->setColumnWidth(3, gameListView->columnWidth(3) * 1.2); // game type width gameListView->setColumnWidth(4, gameListView->columnWidth(4) * 1.4); - if (_room) + if (_room) { gameListView->header()->hideSection(gameListModel->roomColIndex()); + } - if (room) + if (room) { gameTypeMap = gameListModel->getGameTypes().value(room->getRoomId()); + } - if (showFilters && restoresettings) + if (showFilters && restoresettings) { gameListProxyModel->loadFilterParameters(gameTypeMap); + } gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -91,6 +95,7 @@ GameSelector::GameSelector(AbstractClient *_client, bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults(); clearFilterButton->setEnabled(!filtersSetToDefault); connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter); + connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this, &GameSelector::checkClearFilterButtonState); if (room) { createButton = new QPushButton; @@ -109,8 +114,9 @@ GameSelector::GameSelector(AbstractClient *_client, buttonLayout->addWidget(clearFilterButton); } buttonLayout->addStretch(); - if (room) + if (room) { buttonLayout->addWidget(createButton); + } buttonLayout->addWidget(joinButton); if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { buttonLayout->addWidget(joinAsJudgeButton); @@ -176,27 +182,23 @@ void GameSelector::actSetFilter() { DlgFilterGames dlg(gameTypeMap, gameListProxyModel, this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } - gameListProxyModel->setGameFilters( - dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(), - dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(), - dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilters(), dlg.getGameTypeFilter(), - dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(), - dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(), - dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands()); + gameListProxyModel->setGameFilters(dlg.getFilters()); gameListProxyModel->saveFilterParameters(gameTypeMap); - clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults()); - updateTitle(); } +void GameSelector::checkClearFilterButtonState() +{ + clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults()); +} + void GameSelector::actClearFilter() { - clearFilterButton->setEnabled(false); - gameListProxyModel->resetFilterParameters(); gameListProxyModel->saveFilterParameters(gameTypeMap); @@ -376,8 +378,9 @@ void GameSelector::joinGame(const bool asSpectator, const bool asJudge) void GameSelector::disableButtons() { - if (createButton) + if (createButton) { createButton->setEnabled(false); + } joinButton->setEnabled(false); spectateButton->setEnabled(false); @@ -385,8 +388,9 @@ void GameSelector::disableButtons() void GameSelector::enableButtons() { - if (createButton) + if (createButton) { createButton->setEnabled(true); + } // Enable buttons for the currently selected game enableButtonsForIndex(gameListView->currentIndex()); @@ -394,8 +398,9 @@ void GameSelector::enableButtons() void GameSelector::enableButtonsForIndex(const QModelIndex ¤t) { - if (!current.isValid()) + if (!current.isValid()) { return; + } const ServerInfo_Game &game = gameListModel->getGame(current.data(Qt::UserRole).toInt()); bool overrideRestrictions = !tabSupervisor->getAdminLocked(); @@ -408,8 +413,9 @@ void GameSelector::retranslateUi() { filterButton->setText(tr("&Filter games")); clearFilterButton->setText(tr("C&lear filter")); - if (createButton) + if (createButton) { createButton->setText(tr("C&reate")); + } joinButton->setText(tr("&Join")); joinAsJudgeButton->setText(tr("Join as judge")); spectateButton->setText(tr("J&oin as spectator")); diff --git a/cockatrice/src/interface/widgets/server/game_selector.h b/cockatrice/src/interface/widgets/server/game_selector.h index ea0a4feb0..fa91e5f96 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.h +++ b/cockatrice/src/interface/widgets/server/game_selector.h @@ -40,6 +40,7 @@ private slots: * Updates the proxy model with selected filter parameters and refreshes the displayed game list. */ void actSetFilter(); + void checkClearFilterButtonState(); /** * @brief Clears all filters applied to the game list. diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp index daab4d6eb..d668b1c33 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -18,33 +18,31 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(5); + const GameFilterConfigs &filters = model->getFilters(); + searchBar = new QLineEdit(this); - searchBar->setText(model->getCreatorNameFilters().join(", ")); - connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); }); + searchBar->setText(filters.gameNameFilter); + connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { + applyFilters([&](GameFilterConfigs &configs) { configs.gameNameFilter = text; }); + }); hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this); - hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames()); + hideGamesNotCreatedByBuddiesCheckBox->setChecked(filters.hideNotBuddyCreatedGames); connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { - if (checked) { - QStringList buddyNames; - for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) { - buddyNames << QString::fromStdString(buddy.name()); - } - model->setCreatorNameFilters(buddyNames); - } else { - model->setCreatorNameFilters({}); - } + applyFilters([&](GameFilterConfigs &configs) { configs.hideNotBuddyCreatedGames = checked; }); }); hideFullGamesCheckBox = new QCheckBox(this); - hideFullGamesCheckBox->setChecked(model->getHideFullGames()); - connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, - [this](bool checked) { model->setHideFullGames(checked); }); + hideFullGamesCheckBox->setChecked(filters.hideFullGames); + connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + applyFilters([&](GameFilterConfigs &configs) { configs.hideFullGames = checked; }); + }); hideStartedGamesCheckBox = new QCheckBox(this); - hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted()); - connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, - [this](bool checked) { model->setHideGamesThatStarted(checked); }); + hideStartedGamesCheckBox->setChecked(filters.hideGamesThatStarted); + connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + applyFilters([&](GameFilterConfigs &configs) { configs.hideGamesThatStarted = checked; }); + }); filterToFormatComboBox = new QComboBox(this); @@ -57,25 +55,27 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, filterToFormatComboBox->addItem(i.value(), i.key()); // text = name, data = type ID } - QSet currentTypes = model->getGameTypeFilter(); + QSet currentTypes = filters.gameTypeFilter; if (currentTypes.size() == 1) { int typeId = *currentTypes.begin(); int index = filterToFormatComboBox->findData(typeId); - if (index >= 0) + if (index >= 0) { filterToFormatComboBox->setCurrentIndex(index); + } } else { filterToFormatComboBox->setCurrentIndex(0); // "All types" by default } // Update proxy model on selection change connect(filterToFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { - QVariant data = filterToFormatComboBox->itemData(index); - if (!data.isValid()) { - model->setGameTypeFilter({}); // empty = no filter - } else { - int typeId = data.toInt(); - model->setGameTypeFilter({typeId}); - } + applyFilters([&](GameFilterConfigs &configs) { + QVariant data = filterToFormatComboBox->itemData(index); + if (!data.isValid()) { + configs.gameTypeFilter.clear(); + } else { + configs.gameTypeFilter = {data.toInt()}; + } + }); }); hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20); @@ -96,9 +96,47 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, setLayout(mainLayout); + syncFromModel(); + + connect(model, &GamesProxyModel::filtersChanged, this, &GameSelectorQuickFilterToolBar::syncFromModel); + retranslateUi(); } +void GameSelectorQuickFilterToolBar::syncFromModel() +{ + QSignalBlocker b1(searchBar); + QSignalBlocker b2(filterToFormatComboBox); + QSignalBlocker b3(hideGamesNotCreatedByBuddiesCheckBox); + QSignalBlocker b4(hideFullGamesCheckBox); + QSignalBlocker b5(hideStartedGamesCheckBox); + + const GameFilterConfigs filters = model->getFilters(); + + searchBar->setText(filters.gameNameFilter); + + hideGamesNotCreatedByBuddiesCheckBox->setChecked(filters.hideNotBuddyCreatedGames); + hideFullGamesCheckBox->setChecked(filters.hideFullGames); + hideStartedGamesCheckBox->setChecked(filters.hideGamesThatStarted); + + QSet types = filters.gameTypeFilter; + if (types.size() == 1) { + int idx = filterToFormatComboBox->findData(*types.begin()); + filterToFormatComboBox->setCurrentIndex(idx >= 0 ? idx : 0); + } else { + filterToFormatComboBox->setCurrentIndex(0); + } +} + +void GameSelectorQuickFilterToolBar::applyFilters(std::function mutator) +{ + GameFilterConfigs configs = model->getFilters(); + + mutator(configs); + + model->setGameFilters(configs); +} + void GameSelectorQuickFilterToolBar::retranslateUi() { searchBar->setPlaceholderText(tr("Filter by game name...")); diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h index 642fdd1c4..9b7b6e61f 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -18,6 +18,8 @@ public: TabSupervisor *tabSupervisor, GamesProxyModel *model, const QMap &allGameTypes); + void syncFromModel(); + void applyFilters(std::function mutator); void retranslateUi(); private: diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index 05d363fee..1d30aa7ac 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -23,10 +23,6 @@ enum GameListColumn SPECTATORS }; -const int DEFAULT_MAX_PLAYERS_MIN = 1; -const int DEFAULT_MAX_PLAYERS_MAX = 99; -constexpr auto DEFAULT_MAX_GAME_AGE = QTime(); - const QString GamesModel::getGameCreatedString(const int secs) { static const QTime zeroTime{0, 0}; @@ -67,14 +63,18 @@ GamesModel::GamesModel(const QMap &_rooms, const QMap= gameList.size()) || (index.column() >= columnCount())) + } + if ((index.row() >= gameList.size()) || (index.column() >= columnCount())) { return QVariant(); + } const ServerInfo_Game &gameentry = gameList[index.row()]; switch (index.column()) { @@ -130,8 +130,9 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: { QStringList result; GameTypeMap gameTypeMap = gameTypes.value(gameentry.room_id()); - for (int i = gameentry.game_types_size() - 1; i >= 0; --i) + for (int i = gameentry.game_types_size() - 1; i >= 0; --i) { result.append(gameTypeMap.value(gameentry.game_types(i))); + } return result.join(", "); } case Qt::TextAlignmentRole: @@ -144,14 +145,18 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case SORT_ROLE: case Qt::DisplayRole: { QStringList result; - if (gameentry.with_password()) + if (gameentry.with_password()) { result.append(tr("password")); - if (gameentry.only_buddies()) + } + if (gameentry.only_buddies()) { result.append(tr("buddies only")); - if (gameentry.only_registered()) + } + if (gameentry.only_registered()) { result.append(tr("reg. users only")); - if (gameentry.share_decklists_on_load()) + } + if (gameentry.share_decklists_on_load()) { result.append(tr("open decklists")); + } return result.join(", "); } case Qt::DecorationRole: { @@ -209,8 +214,9 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const QVariant GamesModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const { - if ((role != Qt::DisplayRole) && (role != Qt::TextAlignmentRole)) + if ((role != Qt::DisplayRole) && (role != Qt::TextAlignmentRole)) { return QVariant(); + } switch (section) { case ROOM: return tr("Room"); @@ -283,56 +289,26 @@ GamesProxyModel::GamesProxyModel(QObject *parent, const UserListProxy *_userList setDynamicSortFilter(true); } -void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, - bool _hideIgnoredUserGames, - bool _hideFullGames, - bool _hideGamesThatStarted, - bool _hidePasswordProtectedGames, - bool _hideNotBuddyCreatedGames, - bool _hideOpenDecklistGames, - const QString &_gameNameFilter, - const QStringList &_creatorNameFilters, - const QSet &_gameTypeFilter, - int _maxPlayersFilterMin, - int _maxPlayersFilterMax, - const QTime &_maxGameAge, - bool _showOnlyIfSpectatorsCanWatch, - bool _showSpectatorPasswordProtected, - bool _showOnlyIfSpectatorsCanChat, - bool _showOnlyIfSpectatorsCanSeeHands) +void GamesProxyModel::setGameFilters(const GameFilterConfigs &_filters) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) beginFilterChange(); #endif - hideBuddiesOnlyGames = _hideBuddiesOnlyGames; - hideIgnoredUserGames = _hideIgnoredUserGames; - hideFullGames = _hideFullGames; - hideGamesThatStarted = _hideGamesThatStarted; - hidePasswordProtectedGames = _hidePasswordProtectedGames; - hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames; - hideOpenDecklistGames = _hideOpenDecklistGames; - gameNameFilter = _gameNameFilter; - creatorNameFilters = _creatorNameFilters; - gameTypeFilter = _gameTypeFilter; - maxPlayersFilterMin = _maxPlayersFilterMin; - maxPlayersFilterMax = _maxPlayersFilterMax; - maxGameAge = _maxGameAge; - showOnlyIfSpectatorsCanWatch = _showOnlyIfSpectatorsCanWatch; - showSpectatorPasswordProtected = _showSpectatorPasswordProtected; - showOnlyIfSpectatorsCanChat = _showOnlyIfSpectatorsCanChat; - showOnlyIfSpectatorsCanSeeHands = _showOnlyIfSpectatorsCanSeeHands; + filters = _filters; #if (QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)) endFilterChange(QSortFilterProxyModel::Direction::Rows); #else invalidateFilter(); #endif + emit filtersChanged(); } int GamesProxyModel::getNumFilteredGames() const { auto *model = qobject_cast(sourceModel()); - if (!model) + if (!model) { return 0; + } int numFilteredGames = 0; for (int row = 0; row < model->rowCount(); ++row) { @@ -345,18 +321,12 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - setGameFilters(false, false, false, false, false, false, false, QString(), QStringList(), {}, - DEFAULT_MAX_PLAYERS_MIN, DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); + setGameFilters({}); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { - return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && - !hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && - creatorNameFilters.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && - maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && - !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && - !showOnlyIfSpectatorsCanSeeHands; + return filters.isDefault(); } void GamesProxyModel::loadFilterParameters(const QMap &allGameTypes) @@ -372,44 +342,44 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType } } - setGameFilters(gameFilters.isHideBuddiesOnlyGames(), gameFilters.isHideIgnoredUserGames(), - gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(), - gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(), - gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(), - gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(), - gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(), - gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(), - gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()); + setGameFilters({gameFilters.isHideBuddiesOnlyGames(), gameFilters.isHideIgnoredUserGames(), + gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(), + gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(), + gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(), + gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(), + gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(), + gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(), + gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()}); } void GamesProxyModel::saveFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - gameFilters.setHideBuddiesOnlyGames(hideBuddiesOnlyGames); - gameFilters.setHideFullGames(hideFullGames); - gameFilters.setHideGamesThatStarted(hideGamesThatStarted); - gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames); - gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames); - gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames); - gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames); - gameFilters.setGameNameFilter(gameNameFilter); - gameFilters.setCreatorNameFilters(creatorNameFilters); + gameFilters.setHideBuddiesOnlyGames(filters.hideBuddiesOnlyGames); + gameFilters.setHideFullGames(filters.hideFullGames); + gameFilters.setHideGamesThatStarted(filters.hideGamesThatStarted); + gameFilters.setHidePasswordProtectedGames(filters.hidePasswordProtectedGames); + gameFilters.setHideIgnoredUserGames(filters.hideIgnoredUserGames); + gameFilters.setHideNotBuddyCreatedGames(filters.hideNotBuddyCreatedGames); + gameFilters.setHideOpenDecklistGames(filters.hideOpenDecklistGames); + gameFilters.setGameNameFilter(filters.gameNameFilter); + gameFilters.setCreatorNameFilters(filters.creatorNameFilters); QMapIterator gameTypeIterator(allGameTypes); while (gameTypeIterator.hasNext()) { gameTypeIterator.next(); - bool enabled = gameTypeFilter.contains(gameTypeIterator.key()); + bool enabled = filters.gameTypeFilter.contains(gameTypeIterator.key()); gameFilters.setGameTypeEnabled(gameTypeIterator.value(), enabled); } - gameFilters.setMinPlayers(maxPlayersFilterMin); - gameFilters.setMaxPlayers(maxPlayersFilterMax); - gameFilters.setMaxGameAge(maxGameAge); + gameFilters.setMinPlayers(filters.maxPlayersFilterMin); + gameFilters.setMaxPlayers(filters.maxPlayersFilterMax); + gameFilters.setMaxGameAge(filters.maxGameAge); - gameFilters.setShowOnlyIfSpectatorsCanWatch(showOnlyIfSpectatorsCanWatch); - gameFilters.setShowSpectatorPasswordProtected(showSpectatorPasswordProtected); - gameFilters.setShowOnlyIfSpectatorsCanChat(showOnlyIfSpectatorsCanChat); - gameFilters.setShowOnlyIfSpectatorsCanSeeHands(showOnlyIfSpectatorsCanSeeHands); + gameFilters.setShowOnlyIfSpectatorsCanWatch(filters.showOnlyIfSpectatorsCanWatch); + gameFilters.setShowSpectatorPasswordProtected(filters.showSpectatorPasswordProtected); + gameFilters.setShowOnlyIfSpectatorsCanChat(filters.showOnlyIfSpectatorsCanChat); + gameFilters.setShowOnlyIfSpectatorsCanSeeHands(filters.showOnlyIfSpectatorsCanSeeHands); } bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const @@ -425,38 +395,48 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date(); #endif auto *model = qobject_cast(sourceModel()); - if (!model) + if (!model) { return false; + } const ServerInfo_Game &game = model->getGame(sourceRow); - if (hideBuddiesOnlyGames && game.only_buddies()) { + if (filters.hideBuddiesOnlyGames && game.only_buddies()) { return false; } - if (hideOpenDecklistGames && game.share_decklists_on_load()) { + if (filters.hideOpenDecklistGames && game.share_decklists_on_load()) { return false; } - if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) { + if (filters.hideIgnoredUserGames && + userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) { return false; } - if (hideNotBuddyCreatedGames && !userListProxy->isUserBuddy(QString::fromStdString(game.creator_info().name()))) { + if (filters.hideNotBuddyCreatedGames && + !userListProxy->isUserBuddy(QString::fromStdString(game.creator_info().name()))) { return false; } - if (hideFullGames && game.player_count() == game.max_players()) + if (filters.hideFullGames && game.player_count() == game.max_players()) { return false; - if (hideGamesThatStarted && game.started()) + } + if (filters.hideGamesThatStarted && game.started()) { return false; - if (!userListProxy->isOwnUserRegistered()) - if (game.only_registered()) + } + if (!userListProxy->isOwnUserRegistered()) { + if (game.only_registered()) { return false; - if (hidePasswordProtectedGames && game.with_password()) + } + } + if (filters.hidePasswordProtectedGames && game.with_password()) { return false; - if (!gameNameFilter.isEmpty()) - if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) + } + if (!filters.gameNameFilter.isEmpty()) { + if (!QString::fromStdString(game.description()).contains(filters.gameNameFilter, Qt::CaseInsensitive)) { return false; - if (!creatorNameFilters.isEmpty()) { + } + } + if (!filters.creatorNameFilters.isEmpty()) { bool found = false; - for (const auto &createNameFilter : creatorNameFilters) { + for (const auto &createNameFilter : filters.creatorNameFilters) { if (QString::fromStdString(game.creator_info().name()).contains(createNameFilter, Qt::CaseInsensitive)) { found = true; } @@ -467,36 +447,45 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const } QSet gameTypes; - for (int i = 0; i < game.game_types_size(); ++i) + for (int i = 0; i < game.game_types_size(); ++i) { gameTypes.insert(game.game_types(i)); - if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty()) + } + if (!filters.gameTypeFilter.isEmpty() && gameTypes.intersect(filters.gameTypeFilter).isEmpty()) { return false; + } - if (game.max_players() < maxPlayersFilterMin) + if (static_cast(game.max_players()) < filters.maxPlayersFilterMin) { return false; - if (game.max_players() > maxPlayersFilterMax) + } + if (static_cast(game.max_players()) > filters.maxPlayersFilterMax) { return false; + } - if (maxGameAge.isValid()) { + if (filters.maxGameAge.isValid()) { QDateTime now = QDateTime::currentDateTimeUtc(); qint64 signed_start_time = game.start_time(); // cast to 64 bit value to allow signed value QDateTime total = now.addSecs(-signed_start_time); // a 32 bit value would wrap at 2038-1-19 // games shouldn't have negative ages but we'll not filter them // because qtime wraps after a day we consider all games older than a day to be too old - if (total.isValid() && total.date() >= epochDate && (total.date() > epochDate || total.time() > maxGameAge)) { + if (total.isValid() && total.date() >= epochDate && + (total.date() > epochDate || total.time() > filters.maxGameAge)) { return false; } } - if (showOnlyIfSpectatorsCanWatch) { - if (!game.spectators_allowed()) + if (filters.showOnlyIfSpectatorsCanWatch) { + if (!game.spectators_allowed()) { return false; - if (!showSpectatorPasswordProtected && game.spectators_need_password()) + } + if (!filters.showSpectatorPasswordProtected && game.spectators_need_password()) { return false; - if (showOnlyIfSpectatorsCanChat && !game.spectators_can_chat()) + } + if (filters.showOnlyIfSpectatorsCanChat && !game.spectators_can_chat()) { return false; - if (showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient()) + } + if (filters.showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient()) { return false; + } } return true; } diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index 56c806fb6..b5f86c291 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -1,6 +1,7 @@ #ifndef GAMESMODEL_H #define GAMESMODEL_H +#include "game_filter_configs.h" #include "game_type_map.h" #include @@ -121,22 +122,10 @@ private: // - loadFilterParameters() // - saveFilterParameters() // - filterAcceptsRow() - bool hideBuddiesOnlyGames; - bool hideIgnoredUserGames; - bool hideFullGames; - bool hideGamesThatStarted; - bool hidePasswordProtectedGames; - bool hideNotBuddyCreatedGames; - bool hideOpenDecklistGames; - QString gameNameFilter; - QStringList creatorNameFilters; - QSet gameTypeFilter; - quint32 maxPlayersFilterMin, maxPlayersFilterMax; - QTime maxGameAge; - bool showOnlyIfSpectatorsCanWatch; - bool showSpectatorPasswordProtected; - bool showOnlyIfSpectatorsCanChat; - bool showOnlyIfSpectatorsCanSeeHands; + GameFilterConfigs filters; + +signals: + void filtersChanged(); public: /** @@ -147,182 +136,15 @@ public: explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); // Getters for filter parameters - [[nodiscard]] bool getHideBuddiesOnlyGames() const + [[nodiscard]] const GameFilterConfigs &getFilters() const { - return hideBuddiesOnlyGames; - } - [[nodiscard]] bool getHideIgnoredUserGames() const - { - return hideIgnoredUserGames; - } - [[nodiscard]] bool getHideFullGames() const - { - return hideFullGames; - } - [[nodiscard]] bool getHideGamesThatStarted() const - { - return hideGamesThatStarted; - } - [[nodiscard]] bool getHidePasswordProtectedGames() const - { - return hidePasswordProtectedGames; - } - [[nodiscard]] bool getHideNotBuddyCreatedGames() const - { - return hideNotBuddyCreatedGames; - } - [[nodiscard]] bool getHideOpenDecklistGames() const - { - return hideOpenDecklistGames; - } - [[nodiscard]] QString getGameNameFilter() const - { - return gameNameFilter; - } - [[nodiscard]] QStringList getCreatorNameFilters() const - { - return creatorNameFilters; - } - [[nodiscard]] QSet getGameTypeFilter() const - { - return gameTypeFilter; - } - [[nodiscard]] int getMaxPlayersFilterMin() const - { - return maxPlayersFilterMin; - } - [[nodiscard]] int getMaxPlayersFilterMax() const - { - return maxPlayersFilterMax; - } - [[nodiscard]] const QTime &getMaxGameAge() const - { - return maxGameAge; - } - [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const - { - return showOnlyIfSpectatorsCanWatch; - } - [[nodiscard]] bool getShowSpectatorPasswordProtected() const - { - return showSpectatorPasswordProtected; - } - [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const - { - return showOnlyIfSpectatorsCanChat; - } - [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const - { - return showOnlyIfSpectatorsCanSeeHands; + return filters; } /** * @brief Sets all game filters at once. */ - void setGameFilters(bool _hideBuddiesOnlyGames, - bool _hideIgnoredUserGames, - bool _hideFullGames, - bool _hideGamesThatStarted, - bool _hidePasswordProtectedGames, - bool _hideNotBuddyCreatedGames, - bool _hideOpenDecklistGames, - const QString &_gameNameFilter, - const QStringList &_creatorNameFilter, - const QSet &_gameTypeFilter, - int _maxPlayersFilterMin, - int _maxPlayersFilterMax, - const QTime &_maxGameAge, - bool _showOnlyIfSpectatorsCanWatch, - bool _showSpectatorPasswordProtected, - bool _showOnlyIfSpectatorsCanChat, - bool _showOnlyIfSpectatorsCanSeeHands); - - // Individual setters - void setHideBuddiesOnlyGames(bool value) - { - hideBuddiesOnlyGames = value; - refresh(); - } - void setHideIgnoredUserGames(bool value) - { - hideIgnoredUserGames = value; - refresh(); - } - void setHideFullGames(bool value) - { - hideFullGames = value; - refresh(); - } - void setHideGamesThatStarted(bool value) - { - hideGamesThatStarted = value; - refresh(); - } - void setHidePasswordProtectedGames(bool value) - { - hidePasswordProtectedGames = value; - refresh(); - } - void setHideNotBuddyCreatedGames(bool value) - { - hideNotBuddyCreatedGames = value; - refresh(); - } - void setHideOpenDecklistGames(bool value) - { - hideOpenDecklistGames = value; - refresh(); - } - void setGameNameFilter(const QString &value) - { - gameNameFilter = value; - refresh(); - } - void setCreatorNameFilters(const QStringList &values) - { - creatorNameFilters = values; - refresh(); - } - void setGameTypeFilter(const QSet &value) - { - gameTypeFilter = value; - refresh(); - } - void setMaxPlayersFilterMin(int value) - { - maxPlayersFilterMin = value; - refresh(); - } - void setMaxPlayersFilterMax(int value) - { - maxPlayersFilterMax = value; - refresh(); - } - void setMaxGameAge(const QTime &value) - { - maxGameAge = value; - refresh(); - } - void setShowOnlyIfSpectatorsCanWatch(bool value) - { - showOnlyIfSpectatorsCanWatch = value; - refresh(); - } - void setShowSpectatorPasswordProtected(bool value) - { - showSpectatorPasswordProtected = value; - refresh(); - } - void setShowOnlyIfSpectatorsCanChat(bool value) - { - showOnlyIfSpectatorsCanChat = value; - refresh(); - } - void setShowOnlyIfSpectatorsCanSeeHands(bool value) - { - showOnlyIfSpectatorsCanSeeHands = value; - refresh(); - } + void setGameFilters(const GameFilterConfigs &_filters); /** * @brief Returns the number of games filtered out by the current filter. diff --git a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp index f37c957a4..8ebb70e83 100644 --- a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp +++ b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp @@ -37,13 +37,11 @@ void HandlePublicServers::actFinishParsingDownloadedData() QVariantMap jsonMap = jsonResponse.toVariant().toMap(); updateServerINISettings(jsonMap); } else { - qDebug() << "[PUBLIC SERVER HANDLER]" - << "JSON Parsing Error:" << parseError.errorString(); + qDebug() << "[PUBLIC SERVER HANDLER]" << "JSON Parsing Error:" << parseError.errorString(); emit sigPublicServersDownloadedUnsuccessfully(errorCode); } } else { - qDebug() << "[PUBLIC SERVER HANDLER]" - << "Error Downloading Public Servers" << errorCode; + qDebug() << "[PUBLIC SERVER HANDLER]" << "Error Downloading Public Servers" << errorCode; emit sigPublicServersDownloadedUnsuccessfully(errorCode); } diff --git a/cockatrice/src/interface/widgets/server/handle_public_servers.h b/cockatrice/src/interface/widgets/server/handle_public_servers.h index f06bb3ff6..fa924b183 100644 --- a/cockatrice/src/interface/widgets/server/handle_public_servers.h +++ b/cockatrice/src/interface/widgets/server/handle_public_servers.h @@ -1,8 +1,8 @@ /** * @file handle_public_servers.h * @ingroup Server - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_HANDLE_PUBLIC_SERVERS_H #define COCKATRICE_HANDLE_PUBLIC_SERVERS_H diff --git a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp index 6ca680c3f..a6add3fca 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp +++ b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp @@ -22,8 +22,9 @@ RemoteDeckList_TreeModel::DirectoryNode::~DirectoryNode() void RemoteDeckList_TreeModel::DirectoryNode::clearTree() { - for (int i = 0; i < size(); ++i) + for (int i = 0; i < size(); ++i) { delete at(i); + } clear(); } @@ -31,31 +32,37 @@ QString RemoteDeckList_TreeModel::DirectoryNode::getPath() const { if (parent) { QString parentPath = parent->getPath(); - if (parentPath.isEmpty()) + if (parentPath.isEmpty()) { return name; - else + } else { return parentPath + "/" + name; - } else + } + } else { return name; + } } RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeByPath(QStringList path) { QString pathItem; if (parent) { - if (path.isEmpty()) + if (path.isEmpty()) { return this; + } pathItem = path.takeFirst(); - if (pathItem.isEmpty() && name.isEmpty()) + if (pathItem.isEmpty() && name.isEmpty()) { return this; + } } for (int i = 0; i < size(); ++i) { DirectoryNode *node = dynamic_cast(at(i)); - if (!node) + if (!node) { continue; - if (node->getName() == pathItem) + } + if (node->getName() == pathItem) { return node->getNodeByPath(path); + } } return 0; } @@ -66,12 +73,14 @@ RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeModel::DirectoryNode::get DirectoryNode *node = dynamic_cast(at(i)); if (node) { FileNode *result = node->getNodeById(id); - if (result) + if (result) { return result; + } } else { FileNode *file = dynamic_cast(at(i)); - if (file->getId() == id) + if (file->getId() == id) { return file; + } } } return 0; @@ -95,10 +104,11 @@ RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel() int RemoteDeckList_TreeModel::rowCount(const QModelIndex &parent) const { DirectoryNode *node = getNode(parent); - if (node) + if (node) { return node->size(); - else + } else { return 0; + } } int RemoteDeckList_TreeModel::columnCount(const QModelIndex & /*parent*/) const @@ -108,10 +118,12 @@ int RemoteDeckList_TreeModel::columnCount(const QModelIndex & /*parent*/) const QVariant RemoteDeckList_TreeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid()) { return QVariant(); - if (index.column() >= 3) + } + if (index.column() >= 3) { return QVariant(); + } Node *temp = static_cast(index.internalPointer()); FileNode *file = dynamic_cast(temp); @@ -157,8 +169,9 @@ QVariant RemoteDeckList_TreeModel::data(const QModelIndex &index, int role) cons QVariant RemoteDeckList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation != Qt::Horizontal) + if (orientation != Qt::Horizontal) { return QVariant(); + } switch (role) { case Qt::TextAlignmentRole: return section == 1 ? Qt::AlignRight : Qt::AlignLeft; @@ -181,20 +194,23 @@ QVariant RemoteDeckList_TreeModel::headerData(int section, Qt::Orientation orien QModelIndex RemoteDeckList_TreeModel::index(int row, int column, const QModelIndex &parent) const { - if (!hasIndex(row, column, parent)) + if (!hasIndex(row, column, parent)) { return QModelIndex(); + } DirectoryNode *parentNode = getNode(parent); - if (row >= parentNode->size()) + if (row >= parentNode->size()) { return QModelIndex(); + } return createIndex(row, column, parentNode->at(row)); } QModelIndex RemoteDeckList_TreeModel::parent(const QModelIndex &ind) const { - if (!ind.isValid()) + if (!ind.isValid()) { return QModelIndex(); + } return nodeToIndex(static_cast(ind.internalPointer())->getParent()); } @@ -210,8 +226,9 @@ Qt::ItemFlags RemoteDeckList_TreeModel::flags(const QModelIndex &index) const QModelIndex RemoteDeckList_TreeModel::nodeToIndex(Node *node) const { - if (node == nullptr || node == root) + if (node == nullptr || node == root) { return QModelIndex(); + } return createIndex(node->getParent()->indexOf(node), 0, node); } @@ -233,10 +250,11 @@ void RemoteDeckList_TreeModel::addFolderToTree(const ServerInfo_DeckStorage_Tree const int folderItemsSize = folderInfo.items_size(); for (int i = 0; i < folderItemsSize; ++i) { const ServerInfo_DeckStorage_TreeItem &subItem = folderInfo.items(i); - if (subItem.has_folder()) + if (subItem.has_folder()) { addFolderToTree(subItem, newItem); - else + } else { addFileToTree(subItem, newItem); + } } } diff --git a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h index 8254eb57b..3dd91d7a4 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h @@ -2,8 +2,8 @@ * @file remote_decklist_tree_widget.h * @ingroup NetworkingWidgets * @ingroup DeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef REMOTEDECKLIST_TREEWIDGET_H #define REMOTEDECKLIST_TREEWIDGET_H @@ -75,8 +75,9 @@ public: template [[nodiscard]] T getNode(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return dynamic_cast(root); + } return dynamic_cast(static_cast(index.internalPointer())); } diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp index 5152880d9..1f034b767 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp @@ -14,14 +14,16 @@ const int RemoteReplayList_TreeModel::numberOfColumns = 6; RemoteReplayList_TreeModel::MatchNode::MatchNode(const ServerInfo_ReplayMatch &_matchInfo) : RemoteReplayList_TreeModel::Node(QString::fromStdString(_matchInfo.game_name())), matchInfo(_matchInfo) { - for (int i = 0; i < matchInfo.replay_list_size(); ++i) + for (int i = 0; i < matchInfo.replay_list_size(); ++i) { append(new ReplayNode(matchInfo.replay_list(i), this)); + } } RemoteReplayList_TreeModel::MatchNode::~MatchNode() { - for (int i = 0; i < size(); ++i) + for (int i = 0; i < size(); ++i) { delete at(i); + } } void RemoteReplayList_TreeModel::MatchNode::updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo) @@ -45,22 +47,26 @@ RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const { - if (!parent.isValid()) + if (!parent.isValid()) { return replayMatches.size(); + } auto *matchNode = dynamic_cast(static_cast(parent.internalPointer())); - if (matchNode) + if (matchNode) { return matchNode->size(); - else + } else { return 0; + } } QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid()) { return QVariant(); - if (index.column() >= numberOfColumns) + } + if (index.column() >= numberOfColumns) { return QVariant(); + } auto *replayNode = dynamic_cast(static_cast(index.internalPointer())); if (replayNode) { @@ -103,8 +109,9 @@ QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) co return QString::fromStdString(matchInfo.game_name()); case 2: { QStringList playerList; - for (int i = 0; i < matchInfo.player_names_size(); ++i) + for (int i = 0; i < matchInfo.player_names_size(); ++i) { playerList.append(QString::fromStdString(matchInfo.player_names(i))); + } return playerList.join(", "); } case 4: @@ -131,8 +138,9 @@ QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) co QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation != Qt::Horizontal) + if (orientation != Qt::Horizontal) { return QVariant(); + } switch (role) { case Qt::TextAlignmentRole: switch (section) { @@ -167,17 +175,20 @@ QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation ori QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelIndex &parent) const { - if (!hasIndex(row, column, parent)) + if (!hasIndex(row, column, parent)) { return QModelIndex(); + } auto *matchNode = dynamic_cast(static_cast(parent.internalPointer())); if (matchNode) { - if (row >= matchNode->size()) + if (row >= matchNode->size()) { return QModelIndex(); + } return createIndex(row, column, (void *)matchNode->at(row)); } else { - if (row >= replayMatches.size()) + if (row >= replayMatches.size()) { return QModelIndex(); + } return createIndex(row, column, (void *)replayMatches[row]); } } @@ -185,9 +196,9 @@ QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelI QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const { MatchNode const *matchNode = dynamic_cast(static_cast(ind.internalPointer())); - if (matchNode) + if (matchNode) { return QModelIndex(); - else { + } else { auto *replayNode = dynamic_cast(static_cast(ind.internalPointer())); return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode->getParent()); } @@ -195,45 +206,52 @@ QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return Qt::NoItemFlags; + } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } ServerInfo_Replay const *RemoteReplayList_TreeModel::getReplay(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return 0; + } auto *node = dynamic_cast(static_cast(index.internalPointer())); - if (!node) + if (!node) { return 0; + } return &node->getReplayInfo(); } ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return nullptr; + } auto *node = dynamic_cast(static_cast(index.internalPointer())); - if (!node) + if (!node) { return nullptr; + } return &node->getMatchInfo(); } ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getEnclosingReplayMatch(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return nullptr; + } auto *node = dynamic_cast(static_cast(index.internalPointer())); if (!node) { auto *_node = dynamic_cast(static_cast(index.internalPointer())); - if (!_node) + if (!_node) { return nullptr; + } return &_node->getParent()->getMatchInfo(); } return &node->getMatchInfo(); @@ -244,8 +262,9 @@ ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getEnclosingReplayMatc */ void RemoteReplayList_TreeModel::clearAll() { - for (int i = 0; i < replayMatches.size(); ++i) + for (int i = 0; i < replayMatches.size(); ++i) { delete replayMatches[i]; + } replayMatches.clear(); } @@ -275,24 +294,26 @@ void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matc void RemoteReplayList_TreeModel::updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo) { - for (int i = 0; i < replayMatches.size(); ++i) + for (int i = 0; i < replayMatches.size(); ++i) { if (replayMatches[i]->getMatchInfo().game_id() == gameId) { replayMatches[i]->updateMatchInfo(matchInfo); emit dataChanged(createIndex(i, 0, (void *)replayMatches[i]), createIndex(i, numberOfColumns - 1, (void *)replayMatches[i])); break; } + } } void RemoteReplayList_TreeModel::removeMatchInfo(int gameId) { - for (int i = 0; i < replayMatches.size(); ++i) + for (int i = 0; i < replayMatches.size(); ++i) { if (replayMatches[i]->getMatchInfo().game_id() == gameId) { beginRemoveRows(QModelIndex(), i, i); replayMatches.removeAt(i); endRemoveRows(); break; } + } } void RemoteReplayList_TreeModel::replayListFinished(const Response &r) @@ -302,8 +323,9 @@ void RemoteReplayList_TreeModel::replayListFinished(const Response &r) beginResetModel(); clearAll(); - for (int i = 0; i < resp.match_list_size(); ++i) + for (int i = 0; i < resp.match_list_size(); ++i) { replayMatches.append(new MatchNode(resp.match_list(i))); + } endResetModel(); emit treeRefreshed(); diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h index 6d81aefca..10e698aee 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h @@ -3,8 +3,8 @@ * @ingroup DeckStorageWidgets * @ingroup Replays * @ingroup NetworkingWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef REMOTEREPLAYLIST_TREEWIDGET_H #define REMOTEREPLAYLIST_TREEWIDGET_H diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp index 7083ac899..195b1cc8d 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp @@ -192,13 +192,15 @@ void UserContextMenu::banUserHistory_processResponse(const Response &resp) table->setMinimumSize(table->horizontalHeader()->length() + (table->columnCount() * 5), table->verticalHeader()->length() + (table->rowCount() * 3)); table->show(); - } else + } else { QMessageBox::information(static_cast(parent()), tr("Ban History"), tr("User has never been banned.")); + } - } else + } else { QMessageBox::critical(static_cast(parent()), tr("Ban History"), tr("Failed to collect ban information.")); + } } void UserContextMenu::warnUserHistory_processResponse(const Response &resp) @@ -228,13 +230,15 @@ void UserContextMenu::warnUserHistory_processResponse(const Response &resp) table->setMinimumSize(table->horizontalHeader()->length() + (table->columnCount() * 5), table->verticalHeader()->length() + (table->rowCount() * 3)); table->show(); - } else + } else { QMessageBox::information(static_cast(parent()), tr("Warning History"), tr("User has never been warned.")); + } - } else + } else { QMessageBox::critical(static_cast(parent()), tr("Warning History"), tr("Failed to collect warning information.")); + } } void UserContextMenu::getAdminNotes_processResponse(const Response &resp) @@ -297,8 +301,9 @@ void UserContextMenu::warnUser_dialogFinished() { auto *dlg = static_cast(sender()); - if (dlg->getName().isEmpty() || userListProxy->getOwnUsername().simplified().isEmpty()) + if (dlg->getName().isEmpty() || userListProxy->getOwnUsername().simplified().isEmpty()) { return; + } Command_WarnUser cmd; cmd.set_user_name(dlg->getName().toStdString()); diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.h b/cockatrice/src/interface/widgets/server/user/user_context_menu.h index 0056d74ae..b0ff89816 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.h +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.h @@ -1,8 +1,8 @@ /** * @file user_context_menu.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef USER_CONTEXT_MENU_H #define USER_CONTEXT_MENU_H diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp index 9ae4a42c2..e41ae6e75 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp @@ -85,24 +85,15 @@ void UserInfoBox::retranslateUi() avatarButton.setText(tr("Change avatar")); } -/** - * Creates the default profile pic that is used when the user doesn't have a custom pic - */ -static QPixmap createDefaultAvatar(int height, const ServerInfo_User &user) -{ - return UserLevelPixmapGenerator::generatePixmap(height, UserLevelFlags(user.user_level()), user.pawn_colors(), - false, QString::fromStdString(user.privlevel())); -} - void UserInfoBox::updateInfo(const ServerInfo_User &user) { - currentUserInfo = &user; - - const UserLevelFlags userLevel(user.user_level()); + userLevel = UserLevelFlags(user.user_level()); + pawnColors = user.pawn_colors(); + privLevel = QString::fromStdString(user.privlevel()); const std::string &bmp = user.avatar_bmp(); if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { - avatarPixmap = createDefaultAvatar(64, user); + avatarPixmap = UserLevelPixmapGenerator::generatePixmap(64, userLevel, pawnColors, false, privLevel); hasAvatar = false; } else { hasAvatar = true; @@ -120,20 +111,21 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) countryLabel3.setText(""); } - userLevelIcon.setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel, user.pawn_colors(), false, - QString::fromStdString(user.privlevel()))); + userLevelIcon.setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel, pawnColors, false, privLevel)); QString userLevelText; - if (userLevel.testFlag(ServerInfo_User::IsAdmin)) + if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { userLevelText = tr("Administrator"); - else if (userLevel.testFlag(ServerInfo_User::IsModerator)) + } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { userLevelText = tr("Moderator"); - else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) + } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) { userLevelText = tr("Registered user"); - else + } else { userLevelText = tr("Unregistered user"); + } - if (userLevel.testFlag(ServerInfo_User::IsJudge)) + if (userLevel.testFlag(ServerInfo_User::IsJudge)) { userLevelText += " | " + tr("Judge"); + } if (user.has_privlevel() && user.privlevel() != "NONE") { userLevelText += " | " + QString("%1").arg(user.privlevel().c_str()); @@ -152,15 +144,17 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) QString UserInfoBox::getAgeString(int ageSeconds) { QString accountAgeString = tr("Unknown"); - if (ageSeconds <= 0) + if (ageSeconds <= 0) { return accountAgeString; + } // secsSinceEpoch is in utc auto secsSinceEpoch = QDateTime::currentSecsSinceEpoch() - ageSeconds; // the date is in local time, fromSecsSinceEpoch expects a timestamp from utc and converts it to local time auto date = QDateTime::fromSecsSinceEpoch(secsSinceEpoch).date(); - if (!date.isValid()) + if (!date.isValid()) { return accountAgeString; + } // now can be local time as the date is also local time auto now = QDate::currentDate(); @@ -218,8 +212,9 @@ void UserInfoBox::actEditInternal(const Response &r) QString realName = QString::fromStdString(user.real_name()); DlgEditUser dlg(this, email, country, realName); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } Command_AccountEdit cmd; cmd.set_real_name(dlg.getRealName().toStdString()); @@ -231,8 +226,9 @@ void UserInfoBox::actEditInternal(const Response &r) getTextWithMax(this, tr("Enter Password"), tr("Password verification is required in order to change your email address"), QLineEdit::Password, "", &ok); - if (!ok) + if (!ok) { return; + } cmd.set_password_check(password.toStdString()); cmd.set_email(dlg.getEmail().toStdString()); } // servers that support password hash do not require all fields to be filled anymore @@ -250,8 +246,9 @@ void UserInfoBox::actEditInternal(const Response &r) void UserInfoBox::actPassword() { DlgEditPassword dlg(this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } auto oldPassword = dlg.getOldPassword(); auto newPassword = dlg.getNewPassword(); @@ -296,8 +293,9 @@ void UserInfoBox::changePassword(const QString &oldPassword, const QString &newP void UserInfoBox::actAvatar() { DlgEditAvatar dlg(this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } Command_AccountImage cmd; cmd.set_image(dlg.getImage().data(), dlg.getImage().size()); @@ -365,7 +363,7 @@ void UserInfoBox::processAvatarResponse(const Response &r) break; case Response::RespInternalError: default: - QMessageBox::critical(this, tr("Error"), tr("An error occured while trying to updater your avatar.")); + QMessageBox::critical(this, tr("Error"), tr("An error occured while trying to update your avatar.")); break; } } @@ -377,7 +375,7 @@ void UserInfoBox::resizeEvent(QResizeEvent *event) resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { int height = qMin(avatarPic.size().width(), avatarPic.size().height()); - resizedPixmap = createDefaultAvatar(height, *currentUserInfo); + resizedPixmap = UserLevelPixmapGenerator::generatePixmap(height, userLevel, pawnColors, false, privLevel); } avatarPic.setPixmap(resizedPixmap); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.h b/cockatrice/src/interface/widgets/server/user/user_info_box.h index d3d926bed..055ac0096 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.h @@ -1,8 +1,8 @@ /** * @file user_info_box.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef USERINFOBOX_H #define USERINFOBOX_H @@ -11,8 +11,9 @@ #include #include #include +#include +#include -class ServerInfo_User; class AbstractClient; class Response; @@ -27,20 +28,15 @@ private: QPushButton editButton, passwordButton, avatarButton; QPixmap avatarPixmap; bool hasAvatar; - const ServerInfo_User *currentUserInfo; + UserLevelFlags userLevel; + ServerInfo_User::PawnColorsOverride pawnColors; + QString privLevel; static QString getAgeString(int ageSeconds); public: UserInfoBox(AbstractClient *_client, bool editable, QWidget *parent = nullptr, Qt::WindowFlags flags = {}); void retranslateUi(); - - inline static QPair getDaysAndYearsBetween(const QDate &then, const QDate &now) - { - int years = now.addDays(1 - then.dayOfYear()).year() - then.year(); // there is no yearsTo - int days = then.addYears(years).daysTo(now); - return {days, years}; - } private slots: void processResponse(const Response &r); void processEditResponse(const Response &r); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_connection.cpp b/cockatrice/src/interface/widgets/server/user/user_info_connection.cpp index 069e438a9..919bc2707 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_connection.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_connection.cpp @@ -53,8 +53,9 @@ QStringList UserConnection_Information::getServerInfo(const QString &find) for (int i = 0; i < size; i++) { QString _saveName = servers.getValue(QString("saveName%1").arg(i), "server", "server_details").toString(); - if (find != _saveName) + if (find != _saveName) { continue; + } QString serverName = servers.getValue(QString("server%1").arg(i), "server", "server_details").toString(); QString portNum = servers.getValue(QString("port%1").arg(i), "server", "server_details").toString(); @@ -73,8 +74,9 @@ QStringList UserConnection_Information::getServerInfo(const QString &find) break; } - if (_server.empty()) + if (_server.empty()) { qCWarning(UserInfoConnectionLog) << "There was a problem!"; + } return _server; } diff --git a/cockatrice/src/interface/widgets/server/user/user_info_connection.h b/cockatrice/src/interface/widgets/server/user/user_info_connection.h index cfba22346..6278815a9 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_connection.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_connection.h @@ -1,8 +1,8 @@ /** * @file user_info_connection.h * @ingroup Client - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef USERCONNECTION_INFORMATION_H #define USERCONNECTION_INFORMATION_H diff --git a/cockatrice/src/interface/widgets/server/user/user_list_manager.h b/cockatrice/src/interface/widgets/server/user/user_list_manager.h index f378bd08d..f09284bd0 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_manager.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_manager.h @@ -1,8 +1,8 @@ /** * @file user_list_manager.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_USER_LIST_MANAGER_H #define COCKATRICE_USER_LIST_MANAGER_H diff --git a/cockatrice/src/interface/widgets/server/user/user_list_proxy.h b/cockatrice/src/interface/widgets/server/user/user_list_proxy.h index bff76077f..626f89b8f 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_proxy.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_proxy.h @@ -2,8 +2,8 @@ * @file user_list_proxy.h * @ingroup UI * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_USERLISTPROXY_H #define COCKATRICE_USERLISTPROXY_H diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 852743479..11c9b60eb 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -40,8 +40,9 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(par idBanCheckBox->setChecked(true); idBanEdit = new QLineEdit(QString::fromStdString(info.clientid())); idBanEdit->setMaxLength(MAX_NAME_LENGTH); - if (QString::fromStdString(info.clientid()).isEmpty()) + if (QString::fromStdString(info.clientid()).isEmpty()) { idBanCheckBox->setChecked(false); + } QGridLayout *banTypeGrid = new QGridLayout; banTypeGrid->addWidget(nameBanCheckBox, 0, 0); @@ -207,27 +208,30 @@ void BanDialog::okClicked() return; } - if (nameBanCheckBox->isChecked()) + if (nameBanCheckBox->isChecked()) { if (nameBanEdit->text().simplified() == "") { QMessageBox::critical(this, tr("Error"), tr("You must have a value in the name ban when selecting the name ban checkbox.")); return; } + } - if (ipBanCheckBox->isChecked()) + if (ipBanCheckBox->isChecked()) { if (ipBanEdit->text().simplified() == "") { QMessageBox::critical(this, tr("Error"), tr("You must have a value in the ip ban when selecting the ip ban checkbox.")); return; } + } - if (idBanCheckBox->isChecked()) + if (idBanCheckBox->isChecked()) { if (idBanEdit->text().simplified() == "") { QMessageBox::critical( this, tr("Error"), tr("You must have a value in the clientid ban when selecting the clientid ban checkbox.")); return; } + } accept(); } @@ -461,14 +465,15 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online) { const QString userName = QString::fromStdString(user.name()); UserListTWI *item = users.value(userName); - if (item) + if (item) { item->setUserInfo(user); - else { + } else { item = new UserListTWI(user); users.insert(userName, item); userTree->addTopLevelItem(item); - if (online) + if (online) { ++onlineCount; + } updateCount(); } item->setOnline(online); @@ -480,8 +485,9 @@ bool UserListWidget::deleteUser(const QString &userName) if (twi) { users.remove(userName); userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(twi)); - if (twi->data(0, Qt::UserRole + 1).toBool()) + if (twi->data(0, Qt::UserRole + 1).toBool()) { --onlineCount; + } delete twi; updateCount(); return true; @@ -493,22 +499,25 @@ bool UserListWidget::deleteUser(const QString &userName) void UserListWidget::setUserOnline(const QString &userName, bool online) { UserListTWI *twi = users.value(userName); - if (!twi) + if (!twi) { return; + } twi->setOnline(online); - if (online) + if (online) { ++onlineCount; - else + } else { --onlineCount; + } updateCount(); } void UserListWidget::updateCount() { QString str = titleStr; - if ((type == BuddyList) || (type == IgnoreList)) + if ((type == BuddyList) || (type == IgnoreList)) { str = str.arg(onlineCount); + } setTitle(str.arg(userTree->topLevelItemCount())); } diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_widget.h index 036837673..5a8c00d10 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -1,8 +1,8 @@ /** * @file user_list_widget.h * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef USERLIST_H #define USERLIST_H diff --git a/cockatrice/src/interface/widgets/settings_page/abstract_settings_page.h b/cockatrice/src/interface/widgets/settings_page/abstract_settings_page.h new file mode 100644 index 000000000..4cbf2d71a --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/abstract_settings_page.h @@ -0,0 +1,16 @@ +#ifndef COCKATRICE_ABSTRACT_SETTINGS_PAGE_H +#define COCKATRICE_ABSTRACT_SETTINGS_PAGE_H + +#include + +#define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs" +#define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts" +#define WIKI_TRANSLATION_FAQ "https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ" + +class AbstractSettingsPage : public QWidget +{ +public: + virtual void retranslateUi() = 0; +}; + +#endif // COCKATRICE_ABSTRACT_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp new file mode 100644 index 000000000..2d32f3ce1 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -0,0 +1,440 @@ +#include "appearance_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../../client/settings/card_counter_settings.h" +#include "../../palette_editor/palette_editor_dialog.h" +#include "../dialogs/override_printing_warning.h" +#include "../interface/theme_manager.h" +#include "../interface/widgets/general/background_sources.h" + +#include +#include +#include +#include +#include + +AppearanceSettingsPage::AppearanceSettingsPage() +{ + SettingsCache &settings = SettingsCache::instance(); + + // Theme settings + QString themeName = SettingsCache::instance().getThemeName(); + + QStringList themeDirs = themeManager->getAvailableThemes().keys(); + for (int i = 0; i < themeDirs.size(); i++) { + themeBox.addItem(themeDirs[i]); + if (themeDirs[i] == themeName) { + themeBox.setCurrentIndex(i); + } + } + + connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &AppearanceSettingsPage::themeBoxChanged); + connect(&openThemeButton, &QPushButton::clicked, this, &AppearanceSettingsPage::openThemeLocation); + + schemeCombo.addItem(tr("Light"), QStringLiteral("Light")); + schemeCombo.addItem(tr("Dark"), QStringLiteral("Dark")); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + schemeCombo.addItem(tr("System"), QStringLiteral("System")); +#endif + + // Seed from whatever the current theme already has saved + const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); + const ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); + const QString current = cfg.colorScheme; + const int seedIdx = schemeCombo.findData(current); + schemeCombo.setCurrentIndex(seedIdx >= 0 ? seedIdx : 0); + + connect(&schemeCombo, &QComboBox::currentIndexChanged, this, + [this] { themeManager->setColorScheme(schemeCombo.currentData().toString()); }); + + connect(themeManager, &ThemeManager::themeChanged, this, [this, dirPath] { + const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); + const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir); + const QString current = cfg.colorScheme; + + schemeCombo.blockSignals(true); + const int idx = schemeCombo.findData(current); + schemeCombo.setCurrentIndex(idx >= 0 ? idx : 0); + schemeCombo.blockSignals(false); + }); + + connect(&editPaletteButton, &QPushButton::clicked, this, &AppearanceSettingsPage::editPalette); + + auto *themeGrid = new QGridLayout; + themeGrid->addWidget(&themeLabel, 0, 0); + themeGrid->addWidget(&themeBox, 0, 1); + themeGrid->addWidget(&openThemeButton, 1, 1); + themeGrid->addWidget(&schemeComboLabel, 2, 0); + themeGrid->addWidget(&schemeCombo, 2, 1); + themeGrid->addWidget(&editPaletteButton, 3, 1); + + themeGroupBox = new QGroupBox; + themeGroupBox->setLayout(themeGrid); + + // Home tab settings + for (const auto &entry : BackgroundSources::all()) { + homeTabBackgroundSourceBox.addItem(QObject::tr(entry.trKey), QVariant::fromValue(entry.type)); + } + + QString homeTabBackgroundSource = SettingsCache::instance().getHomeTabBackgroundSource(); + int homeTabBackgroundSourceId = + homeTabBackgroundSourceBox.findData(BackgroundSources::fromId(homeTabBackgroundSource)); + if (homeTabBackgroundSourceId != -1) { + homeTabBackgroundSourceBox.setCurrentIndex(homeTabBackgroundSourceId); + } + + connect(&homeTabBackgroundSourceBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + auto type = homeTabBackgroundSourceBox.currentData().value(); + SettingsCache::instance().setHomeTabBackgroundSource(BackgroundSources::toId(type)); + updateHomeTabSettingsVisibility(); + }); + + homeTabBackgroundShuffleFrequencySpinBox.setRange(0, 3600); + homeTabBackgroundShuffleFrequencySpinBox.setSuffix(tr(" seconds")); + homeTabBackgroundShuffleFrequencySpinBox.setValue(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency()); + connect(&homeTabBackgroundShuffleFrequencySpinBox, qOverload(&QSpinBox::valueChanged), + &SettingsCache::instance(), &SettingsCache::setHomeTabBackgroundShuffleFrequency); + + homeTabDisplayCardNameCheckBox.setChecked(settings.getHomeTabDisplayCardName()); + connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setHomeTabDisplayCardName); + + updateHomeTabSettingsVisibility(); + + auto *homeTabGrid = new QGridLayout; + homeTabGrid->addWidget(&homeTabBackgroundSourceLabel, 0, 0); + homeTabGrid->addWidget(&homeTabBackgroundSourceBox, 0, 1); + homeTabGrid->addWidget(&homeTabBackgroundShuffleFrequencyLabel, 1, 0); + homeTabGrid->addWidget(&homeTabBackgroundShuffleFrequencySpinBox, 1, 1); + homeTabGrid->addWidget(&homeTabDisplayCardNameCheckBox, 2, 0, 1, 2); + + homeTabGroupBox = new QGroupBox; + homeTabGroupBox->setLayout(homeTabGrid); + + // Menu settings + showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); + connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); + + showGameSelectorFilterToolbarCheckBox.setChecked(settings.getShowGameSelectorFilterToolbar()); + connect(&showGameSelectorFilterToolbarCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setShowGameSelectorFilterToolbar); + + auto *menuGrid = new QGridLayout; + menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); + menuGrid->addWidget(&showGameSelectorFilterToolbarCheckBox, 1, 0); + + menuGroupBox = new QGroupBox; + menuGroupBox->setLayout(menuGrid); + + // Printings settings + overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(settings.getOverrideAllCardArtWithPersonalPreference()); + connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled); + + bumpSetsWithCardsInDeckToTopCheckBox.setChecked(settings.getBumpSetsWithCardsInDeckToTop()); + connect(&bumpSetsWithCardsInDeckToTopCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setBumpSetsWithCardsInDeckToTop); + + auto *printingsGrid = new QGridLayout; + printingsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 0, 0, 1, 2); + printingsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 1, 0, 1, 2); + + printingsGroupBox = new QGroupBox; + printingsGroupBox->setLayout(printingsGrid); + + // Card rendering + displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames()); + connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames); + + autoRotateSidewaysLayoutCardsCheckBox.setChecked(settings.getAutoRotateSidewaysLayoutCards()); + connect(&autoRotateSidewaysLayoutCardsCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setAutoRotateSidewaysLayoutCards); + + cardScalingCheckBox.setChecked(settings.getScaleCards()); + connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling); + + roundCardCornersCheckBox.setChecked(settings.getRoundCardCorners()); + connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings, &SettingsCache::setRoundCardCorners); + + connect(&maxFontSizeForCardsEdit, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setMaxFontSize); + maxFontSizeForCardsEdit.setValue(settings.getMaxFontSize()); + maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit); + maxFontSizeForCardsEdit.setMinimum(9); + maxFontSizeForCardsEdit.setMaximum(100); + + auto *cardsGrid = new QGridLayout; + cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); + cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); + cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); + cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); + cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1); + cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1); + + cardsGroupBox = new QGroupBox; + cardsGroupBox->setLayout(cardsGrid); + + // Card layout + verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); + verticalCardOverlapPercentBox.setRange(0, 80); + connect(&verticalCardOverlapPercentBox, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setStackCardOverlapPercent); + + cardViewInitialRowsMaxBox.setRange(1, 999); + cardViewInitialRowsMaxBox.setValue(SettingsCache::instance().getCardViewInitialRowsMax()); + connect(&cardViewInitialRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, + &AppearanceSettingsPage::cardViewInitialRowsMaxChanged); + + cardViewExpandedRowsMaxBox.setRange(1, 999); + cardViewExpandedRowsMaxBox.setValue(SettingsCache::instance().getCardViewExpandedRowsMax()); + connect(&cardViewExpandedRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, + &AppearanceSettingsPage::cardViewExpandedRowsMaxChanged); + + auto *cardLayoutGrid = new QGridLayout; + cardLayoutGrid->addWidget(&verticalCardOverlapPercentLabel, 0, 0, 1, 1); + cardLayoutGrid->addWidget(&verticalCardOverlapPercentBox, 0, 1, 1, 1); + cardLayoutGrid->addWidget(&cardViewInitialRowsMaxLabel, 1, 0); + cardLayoutGrid->addWidget(&cardViewInitialRowsMaxBox, 1, 1); + cardLayoutGrid->addWidget(&cardViewExpandedRowsMaxLabel, 2, 0); + cardLayoutGrid->addWidget(&cardViewExpandedRowsMaxBox, 2, 1); + + cardLayoutGroupBox = new QGroupBox; + cardLayoutGroupBox->setLayout(cardLayoutGrid); + + // Card counter colors + + auto *cardCounterColorsLayout = new QGridLayout; + cardCounterColorsLayout->setColumnStretch(1, 1); + cardCounterColorsLayout->setColumnStretch(3, 1); + cardCounterColorsLayout->setColumnStretch(5, 1); + + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + for (int index = 0; index < 6; ++index) { + auto *pushButton = new QPushButton; + pushButton->setStyleSheet(QString("background-color: %1").arg(cardCounterSettings.color(index).name())); + + connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, pushButton, + [index, pushButton](int changedIndex, const QColor &color) { + if (index == changedIndex) { + pushButton->setStyleSheet(QString("background-color: %1").arg(color.name())); + } + }); + + connect(pushButton, &QPushButton::clicked, this, [index, this]() { + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + + auto newColor = QColorDialog::getColor(cardCounterSettings.color(index), this); + if (!newColor.isValid()) { + return; + } + + cardCounterSettings.setColor(index, newColor); + }); + + auto *colorName = new QLabel; + cardCounterNames.append(colorName); + + int row = index / 3; + int column = 2 * (index % 3); + + cardCounterColorsLayout->addWidget(pushButton, row, column); + cardCounterColorsLayout->addWidget(colorName, row, column + 1); + } + + auto *cardCountersLayout = new QVBoxLayout; + cardCountersLayout->addLayout(cardCounterColorsLayout, 1); + + cardCountersGroupBox = new QGroupBox; + cardCountersGroupBox->setLayout(cardCountersLayout); + + // Hand layout + horizontalHandCheckBox.setChecked(settings.getHorizontalHand()); + connect(&horizontalHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setHorizontalHand); + + leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified()); + connect(&leftJustifiedHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setLeftJustified); + + auto *handGrid = new QGridLayout; + handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2); + handGrid->addWidget(&leftJustifiedHandCheckBox, 1, 0, 1, 2); + + handGroupBox = new QGroupBox; + handGroupBox->setLayout(handGrid); + + // table grid layout + invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate()); + connect(&invertVerticalCoordinateCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setInvertVerticalCoordinate); + + minPlayersForMultiColumnLayoutEdit.setMinimum(2); + minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); + connect(&minPlayersForMultiColumnLayoutEdit, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setMinPlayersForMultiColumnLayout); + minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit); + + auto *tableGrid = new QGridLayout; + tableGrid->addWidget(&invertVerticalCoordinateCheckBox, 0, 0, 1, 2); + tableGrid->addWidget(&minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1); + tableGrid->addWidget(&minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1); + + tableGroupBox = new QGroupBox; + tableGroupBox->setLayout(tableGrid); + + // putting it all together + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(themeGroupBox); + mainLayout->addWidget(homeTabGroupBox); + mainLayout->addWidget(menuGroupBox); + mainLayout->addWidget(printingsGroupBox); + mainLayout->addWidget(cardsGroupBox); + mainLayout->addWidget(cardLayoutGroupBox); + mainLayout->addWidget(cardCountersGroupBox); + mainLayout->addWidget(handGroupBox); + mainLayout->addWidget(tableGroupBox); + mainLayout->addStretch(); + + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &AppearanceSettingsPage::retranslateUi); + retranslateUi(); +} + +void AppearanceSettingsPage::themeBoxChanged(int index) +{ + QStringList themeDirs = themeManager->getAvailableThemes().keys(); + if (index >= 0 && index < themeDirs.count()) { + SettingsCache::instance().setThemeName(themeDirs.at(index)); + } +} + +void AppearanceSettingsPage::openThemeLocation() +{ + QString dir = SettingsCache::instance().getThemesPath(); + QDir dirDir = dir; + dirDir.cdUp(); + // open if dir exists, create if parent dir does exist + if (dirDir.exists() && dirDir.mkpath(dir)) { + QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + } else { + QMessageBox::critical(this, tr("Error"), tr("Could not create themes directory at '%1'.").arg(dir)); + } +} + +void AppearanceSettingsPage::editPalette() +{ + PaletteEditorDialog dlg(themeManager->getCurrentThemePath(), SettingsCache::instance().getThemeName(), this); + dlg.exec(); +} + +void AppearanceSettingsPage::updateHomeTabSettingsVisibility() +{ + bool visible = + SettingsCache::instance().getHomeTabBackgroundSource() != BackgroundSources::toId(BackgroundSources::Theme); + + homeTabBackgroundShuffleFrequencyLabel.setVisible(visible); + homeTabBackgroundShuffleFrequencySpinBox.setVisible(visible); + homeTabDisplayCardNameCheckBox.setVisible(visible); +} + +void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value) +{ + SettingsCache::instance().setShowShortcuts(value); + qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, value == 0); // 0 = unchecked +} + +void AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T value) +{ + bool enable = static_cast(value); + + bool accepted = OverridePrintingWarning::execMessageBox(this, enable); + + if (!accepted) { + // If user cancels, revert the checkbox/state back + QTimer::singleShot(0, this, [this, enable]() { + overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(true); + overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(!enable); + overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(false); + }); + } +} + +/** + * Updates the settings for cardViewInitialRowsMax. + * Forces expanded rows max to always be >= initial rows max + * @param value The new value + */ +void AppearanceSettingsPage::cardViewInitialRowsMaxChanged(int value) +{ + SettingsCache::instance().setCardViewInitialRowsMax(value); + if (cardViewExpandedRowsMaxBox.value() < value) { + cardViewExpandedRowsMaxBox.setValue(value); + } +} + +/** + * Updates the settings for cardViewExpandedRowsMax. + * Forces initial rows max to always be <= expanded rows max + * @param value The new value + */ +void AppearanceSettingsPage::cardViewExpandedRowsMaxChanged(int value) +{ + SettingsCache::instance().setCardViewExpandedRowsMax(value); + if (cardViewInitialRowsMaxBox.value() > value) { + cardViewInitialRowsMaxBox.setValue(value); + } +} + +void AppearanceSettingsPage::retranslateUi() +{ + themeGroupBox->setTitle(tr("Theme settings")); + themeLabel.setText(tr("Current theme:")); + openThemeButton.setText(tr("Open themes folder")); + schemeComboLabel.setText(tr("Active theme palette:")); + editPaletteButton.setText(tr("Edit theme palette")); + + homeTabGroupBox->setTitle(tr("Home tab settings")); + homeTabBackgroundSourceLabel.setText(tr("Home tab background source:")); + homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:")); + homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled")); + homeTabDisplayCardNameCheckBox.setText(tr("Display card name of background in bottom right")); + + menuGroupBox->setTitle(tr("Menu settings")); + showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); + showGameSelectorFilterToolbarCheckBox.setText(tr("Show game filter toolbar above list in room tab")); + + printingsGroupBox->setTitle(tr("Card printings")); + overrideAllCardArtWithPersonalPreferenceCheckBox.setText( + tr("Override all card art with personal set preference (Pre-ProviderID change behavior)")); + bumpSetsWithCardsInDeckToTopCheckBox.setText( + tr("Bump sets that the deck contains cards from to the top in the printing selector")); + + cardsGroupBox->setTitle(tr("Card rendering")); + displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); + autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); + cardScalingCheckBox.setText(tr("Scale cards on mouse over")); + roundCardCornersCheckBox.setText(tr("Use rounded card corners")); + maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:")); + + cardLayoutGroupBox->setTitle(tr("Card layout")); + verticalCardOverlapPercentLabel.setText( + tr("Minimum overlap percentage of cards on the stack and in vertical hand")); + cardViewInitialRowsMaxLabel.setText(tr("Maximum initial height for card view window:")); + cardViewInitialRowsMaxBox.setSuffix(tr(" rows")); + cardViewExpandedRowsMaxLabel.setText(tr("Maximum expanded height for card view window:")); + cardViewExpandedRowsMaxBox.setSuffix(tr(" rows")); + + cardCountersGroupBox->setTitle(tr("Card counters")); + + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + for (int index = 0; index < cardCounterNames.size(); ++index) { + cardCounterNames[index]->setText(tr("Counter %1").arg(cardCounterSettings.displayName(index))); + } + + handGroupBox->setTitle(tr("Hand layout")); + horizontalHandCheckBox.setText(tr("Display hand horizontally (wastes space)")); + leftJustifiedHandCheckBox.setText(tr("Enable left justification")); + + tableGroupBox->setTitle(tr("Table grid layout")); + invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate")); + minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h new file mode 100644 index 000000000..9ed27be4d --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h @@ -0,0 +1,77 @@ +#ifndef COCKATRICE_APPEARANCE_SETTINGS_PAGE_H +#define COCKATRICE_APPEARANCE_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include +#include +#include + +class AppearanceSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +private slots: + void themeBoxChanged(int index); + void openThemeLocation(); + void editPalette(); + void updateHomeTabSettingsVisibility(); + void showShortcutsChanged(QT_STATE_CHANGED_T enabled); + void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled); + + void cardViewInitialRowsMaxChanged(int value); + void cardViewExpandedRowsMaxChanged(int value); + +private: + QLabel themeLabel; + QComboBox themeBox; + QPushButton openThemeButton; + QLabel schemeComboLabel; + QComboBox schemeCombo; + QPushButton editPaletteButton; + QLabel homeTabBackgroundSourceLabel; + QComboBox homeTabBackgroundSourceBox; + QLabel homeTabBackgroundShuffleFrequencyLabel; + QSpinBox homeTabBackgroundShuffleFrequencySpinBox; + QCheckBox homeTabDisplayCardNameCheckBox; + QLabel minPlayersForMultiColumnLayoutLabel; + QLabel maxFontSizeForCardsLabel; + QCheckBox showShortcutsCheckBox; + QCheckBox showGameSelectorFilterToolbarCheckBox; + QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; + QCheckBox bumpSetsWithCardsInDeckToTopCheckBox; + QCheckBox displayCardNamesCheckBox; + QCheckBox autoRotateSidewaysLayoutCardsCheckBox; + QCheckBox cardScalingCheckBox; + QCheckBox roundCardCornersCheckBox; + QLabel verticalCardOverlapPercentLabel; + QSpinBox verticalCardOverlapPercentBox; + QLabel cardViewInitialRowsMaxLabel; + QSpinBox cardViewInitialRowsMaxBox; + QLabel cardViewExpandedRowsMaxLabel; + QSpinBox cardViewExpandedRowsMaxBox; + QCheckBox horizontalHandCheckBox; + QCheckBox leftJustifiedHandCheckBox; + QCheckBox invertVerticalCoordinateCheckBox; + QGroupBox *themeGroupBox; + QGroupBox *homeTabGroupBox; + QGroupBox *menuGroupBox; + QGroupBox *printingsGroupBox; + QGroupBox *cardsGroupBox; + QGroupBox *cardLayoutGroupBox; + QGroupBox *handGroupBox; + QGroupBox *tableGroupBox; + QGroupBox *cardCountersGroupBox; + QList cardCounterNames; + QSpinBox minPlayersForMultiColumnLayoutEdit; + QSpinBox maxFontSizeForCardsEdit; + +public: + AppearanceSettingsPage(); + void retranslateUi() override; +}; + +#endif // COCKATRICE_APPEARANCE_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp new file mode 100644 index 000000000..b7d699ebc --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp @@ -0,0 +1,241 @@ +#include "deck_editor_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "update/card_spoiler/spoiler_background_updater.h" + +#include +#include +#include +#include +#include +#include +#include + +DeckEditorSettingsPage::DeckEditorSettingsPage() +{ + picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload()); + connect(&picDownloadCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPicDownload); + + urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + urlLinkLabel.setOpenExternalLinks(true); + + connect(&resetDownloadURLs, &QPushButton::clicked, this, &DeckEditorSettingsPage::resetDownloadedURLsButtonClicked); + + auto *lpGeneralGrid = new QGridLayout; + auto *lpSpoilerGrid = new QGridLayout; + + mcDownloadSpoilersCheckBox.setChecked(SettingsCache::instance().getDownloadSpoilersStatus()); + + mpSpoilerSavePathLineEdit = new QLineEdit(SettingsCache::instance().getSpoilerCardDatabasePath()); + mpSpoilerSavePathLineEdit->setReadOnly(true); + mpSpoilerPathButton = new QPushButton("..."); + connect(mpSpoilerPathButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::spoilerPathButtonClicked); + + updateNowButton = new QPushButton; + updateNowButton->setFixedWidth(150); + connect(updateNowButton, &QPushButton::clicked, this, &DeckEditorSettingsPage::updateSpoilers); + + // Update the GUI depending on if the box is ticked or not + setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); + + urlList = new QListWidget; + urlList->setSelectionMode(QAbstractItemView::SingleSelection); + urlList->setAlternatingRowColors(true); + urlList->setDragEnabled(true); + urlList->setDragDropMode(QAbstractItemView::InternalMove); + connect(urlList->model(), &QAbstractItemModel::rowsMoved, this, &DeckEditorSettingsPage::urlListChanged); + + urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); + + aAdd = new QAction(this); + aAdd->setIcon(QPixmap("theme:icons/increment")); + connect(aAdd, &QAction::triggered, this, &DeckEditorSettingsPage::actAddURL); + + aEdit = new QAction(this); + aEdit->setIcon(QPixmap("theme:icons/pencil")); + connect(aEdit, &QAction::triggered, this, &DeckEditorSettingsPage::actEditURL); + + aRemove = new QAction(this); + aRemove->setIcon(QPixmap("theme:icons/decrement")); + connect(aRemove, &QAction::triggered, this, &DeckEditorSettingsPage::actRemoveURL); + + auto *urlToolBar = new QToolBar; + urlToolBar->setOrientation(Qt::Vertical); + urlToolBar->addAction(aAdd); + urlToolBar->addAction(aRemove); + urlToolBar->addAction(aEdit); + urlToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + + auto *urlListLayout = new QHBoxLayout; + urlListLayout->addWidget(urlToolBar); + urlListLayout->addWidget(urlList); + + // Top Layout + lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); + lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); + lpGeneralGrid->addLayout(urlListLayout, 1, 0, 1, 2); + lpGeneralGrid->addWidget(&urlLinkLabel, 4, 0); + + // Spoiler Layout + lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); + lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0); + lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1); + lpSpoilerGrid->addWidget(mpSpoilerPathButton, 1, 2); + lpSpoilerGrid->addWidget(&lastUpdatedLabel, 2, 0); + lpSpoilerGrid->addWidget(updateNowButton, 2, 1); + lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop); + + // On a change to the checkbox, hide/un-hide the other fields + connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, &SettingsCache::instance(), + &SettingsCache::setDownloadSpoilerStatus); + connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, this, &DeckEditorSettingsPage::setSpoilersEnabled); + + mpGeneralGroupBox = new QGroupBox; + mpGeneralGroupBox->setLayout(lpGeneralGrid); + + mpSpoilerGroupBox = new QGroupBox; + mpSpoilerGroupBox->setLayout(lpSpoilerGrid); + + auto *lpMainLayout = new QVBoxLayout; + lpMainLayout->addWidget(mpGeneralGroupBox); + lpMainLayout->addWidget(mpSpoilerGroupBox); + + setLayout(lpMainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DeckEditorSettingsPage::retranslateUi); + retranslateUi(); +} + +void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() +{ + SettingsCache::instance().downloads().resetToDefaultURLs(); + urlList->clear(); + urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); + QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); +} + +void DeckEditorSettingsPage::actAddURL() +{ + bool ok; + QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok); + if (ok) { + urlList->addItem(msg); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actRemoveURL() +{ + if (urlList->currentItem() != nullptr) { + delete urlList->takeItem(urlList->currentRow()); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actEditURL() +{ + if (urlList->currentItem()) { + QString oldText = urlList->currentItem()->text(); + bool ok; + QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok); + if (ok) { + urlList->currentItem()->setText(msg); + storeSettings(); + } + } +} + +void DeckEditorSettingsPage::storeSettings() +{ + qInfo() << "URL Priority Reset"; + + QStringList downloadUrls; + for (int i = 0; i < urlList->count(); i++) { + qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); + downloadUrls << urlList->item(i)->text(); + } + SettingsCache::instance().downloads().setDownloadUrls(downloadUrls); +} + +void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) +{ + storeSettings(); +} + +void DeckEditorSettingsPage::updateSpoilers() +{ + // Disable the button so the user can only press it once at a time + updateNowButton->setDisabled(true); + updateNowButton->setText(tr("Updating...")); + + // Create a new SBU that will act as if the client was just reloaded + auto *sbu = new SpoilerBackgroundUpdater(); + connect(sbu, &SpoilerBackgroundUpdater::spoilerCheckerDone, this, &DeckEditorSettingsPage::unlockSettings); + connect(sbu, &SpoilerBackgroundUpdater::spoilersUpdatedSuccessfully, this, &DeckEditorSettingsPage::unlockSettings); +} + +void DeckEditorSettingsPage::unlockSettings() +{ + updateNowButton->setDisabled(false); + updateNowButton->setText(tr("Update Spoilers")); +} + +QString DeckEditorSettingsPage::getLastUpdateTime() +{ + QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath(); + QFileInfo fi(fileName); + QDir fileDir(fi.path()); + QFile file(fileName); + + if (file.exists()) { + return fi.lastModified().toString("MMM d, hh:mm"); + } + + return QString(); +} + +void DeckEditorSettingsPage::spoilerPathButtonClicked() +{ + QString lsPath = QFileDialog::getExistingDirectory(this, tr("Choose path"), mpSpoilerSavePathLineEdit->text()); + if (lsPath.isEmpty()) { + return; + } + + mpSpoilerSavePathLineEdit->setText(lsPath + "/spoiler.xml"); + SettingsCache::instance().setSpoilerDatabasePath(lsPath + "/spoiler.xml"); +} + +void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput) +{ + msDownloadSpoilersLabel.setEnabled(anInput); + mcSpoilerSaveLabel.setEnabled(anInput); + mpSpoilerSavePathLineEdit->setEnabled(anInput); + mpSpoilerPathButton->setEnabled(anInput); + lastUpdatedLabel.setEnabled(anInput); + updateNowButton->setEnabled(anInput); + infoOnSpoilersLabel.setEnabled(anInput); + + if (!anInput) { + SpoilerBackgroundUpdater::deleteSpoilerFile(); + } +} + +void DeckEditorSettingsPage::retranslateUi() +{ + mpGeneralGroupBox->setTitle(tr("URL Download Priority")); + mpSpoilerGroupBox->setTitle(tr("Spoilers")); + mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically")); + mcSpoilerSaveLabel.setText(tr("Spoiler Location:")); + lastUpdatedLabel.setText(tr("Last Change") + ": " + getLastUpdateTime()); + infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" + + tr("Press the button to manually update without relaunching") + "\n\n" + + tr("Do not close settings until manual update is complete")); + picDownloadCheckBox.setText(tr("Download card pictures on the fly")); + urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); + resetDownloadURLs.setText(tr("Reset Download URLs")); + updateNowButton->setText(tr("Update Spoilers")); + aAdd->setText(tr("Add New URL")); + aEdit->setText(tr("Edit URL")); + aRemove->setText(tr("Remove URL")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.h b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.h new file mode 100644 index 000000000..5db009c8a --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.h @@ -0,0 +1,51 @@ +#ifndef COCKATRICE_DECK_EDITOR_SETTINGS_PAGE_H +#define COCKATRICE_DECK_EDITOR_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include + +class DeckEditorSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + DeckEditorSettingsPage(); + void retranslateUi() override; + QString getLastUpdateTime(); + +private slots: + void storeSettings(); + void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int); + void setSpoilersEnabled(bool); + void spoilerPathButtonClicked(); + void updateSpoilers(); + void unlockSettings(); + void actAddURL(); + void actRemoveURL(); + void actEditURL(); + void resetDownloadedURLsButtonClicked(); + +private: + QPushButton resetDownloadURLs; + QLabel urlLinkLabel; + QCheckBox picDownloadCheckBox; + QListWidget *urlList; + QAction *aAdd, *aEdit, *aRemove; + QCheckBox mcDownloadSpoilersCheckBox; + QLabel msDownloadSpoilersLabel; + QGroupBox *mpGeneralGroupBox; + QGroupBox *mpSpoilerGroupBox; + + QLineEdit *mpSpoilerSavePathLineEdit; + QLabel mcSpoilerSaveLabel; + QLabel lastUpdatedLabel; + QLabel infoOnSpoilersLabel; + QPushButton *mpSpoilerPathButton; + QPushButton *updateNowButton; +}; + +#endif // COCKATRICE_DECK_EDITOR_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp new file mode 100644 index 000000000..9faf67578 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp @@ -0,0 +1,407 @@ +#include "general_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../main.h" +#include "update/client/release_channel.h" + +#include +#include +#include +#include +#include + +enum startupCardUpdateCheckBehaviorIndex +{ + startupCardUpdateCheckBehaviorIndexNone, + startupCardUpdateCheckBehaviorIndexPrompt, + startupCardUpdateCheckBehaviorIndexAlways +}; + +GeneralSettingsPage::GeneralSettingsPage() +{ + // language settings + QStringList languageCodes = findQmFiles(); + for (const QString &code : languageCodes) { + QString langName = languageName(code); + languageBox.addItem(langName, code); + } + + QString setLanguage = QCoreApplication::translate("i18n", DEFAULT_LANG_NAME); + int index = languageBox.findText(setLanguage, Qt::MatchExactly); + if (index == -1) { + qWarning() << "could not find language" << setLanguage; + } else { + languageBox.setCurrentIndex(index); + } + + advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + advertiseTranslationPageLabel.setOpenExternalLinks(true); + + connect(&languageBox, qOverload(&QComboBox::currentIndexChanged), this, + &GeneralSettingsPage::languageBoxChanged); + + auto *languageGrid = new QGridLayout; + languageGrid->addWidget(&languageLabel, 0, 0); + languageGrid->addWidget(&languageBox, 0, 1); + languageGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight); + + languageGroupBox = new QGroupBox; + languageGroupBox->setLayout(languageGrid); + + // version settings + SettingsCache &settings = SettingsCache::instance(); + startupUpdateCheckCheckBox.setChecked(settings.getCheckUpdatesOnStartup()); + + connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setCheckUpdatesOnStartup); + + updateNotificationCheckBox.setChecked(settings.getNotifyAboutUpdates()); + + connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate); + + connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setNotifyAboutNewVersion); + + auto *versionGrid = new QGridLayout; + versionGrid->addWidget(&updateReleaseChannelLabel, 0, 0); + versionGrid->addWidget(&updateReleaseChannelBox, 0, 1); + versionGrid->addWidget(&startupUpdateCheckCheckBox, 1, 0, 1, 2); + versionGrid->addWidget(&updateNotificationCheckBox, 2, 0, 1, 2); + versionGrid->addWidget(&newVersionOracleCheckBox, 3, 0, 1, 2); + + versionGroupBox = new QGroupBox; + versionGroupBox->setLayout(versionGrid); + + // card database settings + startupCardUpdateCheckBehaviorSelector.addItem(""); // these will be set in retranslateUI + startupCardUpdateCheckBehaviorSelector.addItem(""); + startupCardUpdateCheckBehaviorSelector.addItem(""); + if (SettingsCache::instance().getStartupCardUpdateCheckPromptForUpdate()) { + startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexPrompt); + } else if (SettingsCache::instance().getStartupCardUpdateCheckAlwaysUpdate()) { + startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexAlways); + } else { + startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexNone); + } + + connect(&startupCardUpdateCheckBehaviorSelector, QOverload::of(&QComboBox::currentIndexChanged), this, + [](int index) { + SettingsCache::instance().setStartupCardUpdateCheckPromptForUpdate( + index == startupCardUpdateCheckBehaviorIndexPrompt); + SettingsCache::instance().setStartupCardUpdateCheckAlwaysUpdate( + index == startupCardUpdateCheckBehaviorIndexAlways); + }); + + cardUpdateCheckIntervalSpinBox.setMinimum(1); + cardUpdateCheckIntervalSpinBox.setMaximum(30); + cardUpdateCheckIntervalSpinBox.setValue(settings.getCardUpdateCheckInterval()); + + connect(&cardUpdateCheckIntervalSpinBox, qOverload(&QSpinBox::valueChanged), &settings, + &SettingsCache::setCardUpdateCheckInterval); + + newVersionOracleCheckBox.setChecked(settings.getNotifyAboutNewVersion()); + + auto *cardDatabaseGrid = new QGridLayout; + cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 0, 0); + cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 0, 1); + cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalLabel, 1, 0); + cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 1, 1); + cardDatabaseGrid->addWidget(&lastCardUpdateCheckDateLabel, 2, 1); + + cardDatabaseGroupBox = new QGroupBox; + cardDatabaseGroupBox->setLayout(cardDatabaseGrid); + + // startup settings + showTipsOnStartup.setChecked(settings.getShowTipsOnStartup()); + + connect(&showTipsOnStartup, &QCheckBox::clicked, &settings, &SettingsCache::setShowTipsOnStartup); + + auto *startupGrid = new QGridLayout; + startupGrid->addWidget(&showTipsOnStartup, 0, 0, 1, 2); + + startupGroupBox = new QGroupBox; + startupGroupBox->setLayout(startupGrid); + + // paths settings + deckPathEdit = new QLineEdit(settings.getDeckPath()); + deckPathEdit->setReadOnly(true); + auto *deckPathButton = new QPushButton("..."); + connect(deckPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::deckPathButtonClicked); + + filtersPathEdit = new QLineEdit(settings.getFiltersPath()); + filtersPathEdit->setReadOnly(true); + auto *filtersPathButton = new QPushButton("..."); + connect(filtersPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::filtersPathButtonClicked); + + replaysPathEdit = new QLineEdit(settings.getReplaysPath()); + replaysPathEdit->setReadOnly(true); + auto *replaysPathButton = new QPushButton("..."); + connect(replaysPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::replaysPathButtonClicked); + + picsPathEdit = new QLineEdit(settings.getPicsPath()); + picsPathEdit->setReadOnly(true); + auto *picsPathButton = new QPushButton("..."); + connect(picsPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::picsPathButtonClicked); + + cardDatabasePathEdit = new QLineEdit(settings.getCardDatabasePath()); + cardDatabasePathEdit->setReadOnly(true); + auto *cardDatabasePathButton = new QPushButton("..."); + connect(cardDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::cardDatabasePathButtonClicked); + + customCardDatabasePathEdit = new QLineEdit(settings.getCustomCardDatabasePath()); + customCardDatabasePathEdit->setReadOnly(true); + auto *customCardDatabasePathButton = new QPushButton("..."); + connect(customCardDatabasePathButton, &QPushButton::clicked, this, + &GeneralSettingsPage::customCardDatabaseButtonClicked); + + tokenDatabasePathEdit = new QLineEdit(settings.getTokenDatabasePath()); + tokenDatabasePathEdit->setReadOnly(true); + auto *tokenDatabasePathButton = new QPushButton("..."); + connect(tokenDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::tokenDatabasePathButtonClicked); + + // Required init here to avoid crashing on Portable builds + resetAllPathsButton = new QPushButton; + + bool isPortable = settings.getIsPortableBuild(); + if (isPortable) { + deckPathEdit->setEnabled(false); + filtersPathEdit->setEnabled(false); + replaysPathEdit->setEnabled(false); + picsPathEdit->setEnabled(false); + cardDatabasePathEdit->setEnabled(false); + customCardDatabasePathEdit->setEnabled(false); + tokenDatabasePathEdit->setEnabled(false); + + deckPathButton->setVisible(false); + replaysPathButton->setVisible(false); + picsPathButton->setVisible(false); + cardDatabasePathButton->setVisible(false); + customCardDatabasePathButton->setVisible(false); + tokenDatabasePathButton->setVisible(false); + } else { + connect(resetAllPathsButton, &QPushButton::clicked, this, &GeneralSettingsPage::resetAllPathsClicked); + allPathsResetLabel = new QLabel(tr("All paths have been reset")); + allPathsResetLabel->setVisible(false); + } + + auto *pathsGrid = new QGridLayout; + pathsGrid->addWidget(&deckPathLabel, 0, 0); + pathsGrid->addWidget(deckPathEdit, 0, 1); + pathsGrid->addWidget(deckPathButton, 0, 2); + pathsGrid->addWidget(&filtersPathLabel, 1, 0); + pathsGrid->addWidget(filtersPathEdit, 1, 1); + pathsGrid->addWidget(filtersPathButton, 1, 2); + pathsGrid->addWidget(&replaysPathLabel, 2, 0); + pathsGrid->addWidget(replaysPathEdit, 2, 1); + pathsGrid->addWidget(replaysPathButton, 2, 2); + pathsGrid->addWidget(&picsPathLabel, 3, 0); + pathsGrid->addWidget(picsPathEdit, 3, 1); + pathsGrid->addWidget(picsPathButton, 3, 2); + pathsGrid->addWidget(&cardDatabasePathLabel, 4, 0); + pathsGrid->addWidget(cardDatabasePathEdit, 4, 1); + pathsGrid->addWidget(cardDatabasePathButton, 4, 2); + pathsGrid->addWidget(&customCardDatabasePathLabel, 5, 0); + pathsGrid->addWidget(customCardDatabasePathEdit, 5, 1); + pathsGrid->addWidget(customCardDatabasePathButton, 5, 2); + pathsGrid->addWidget(&tokenDatabasePathLabel, 6, 0); + pathsGrid->addWidget(tokenDatabasePathEdit, 6, 1); + pathsGrid->addWidget(tokenDatabasePathButton, 6, 2); + if (!isPortable) { + pathsGrid->addWidget(resetAllPathsButton, 7, 0); + pathsGrid->addWidget(allPathsResetLabel, 7, 1); + } + pathsGroupBox = new QGroupBox; + pathsGroupBox->setLayout(pathsGrid); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(languageGroupBox); + mainLayout->addWidget(versionGroupBox); + mainLayout->addWidget(cardDatabaseGroupBox); + mainLayout->addWidget(startupGroupBox); + mainLayout->addWidget(pathsGroupBox); + mainLayout->addStretch(); + + GeneralSettingsPage::retranslateUi(); + + // connect the ReleaseChannel combo box only after the entries are inserted in retranslateUi + connect(&updateReleaseChannelBox, qOverload(&QComboBox::currentIndexChanged), &settings, + &SettingsCache::setUpdateReleaseChannelIndex); + updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex()); + + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &GeneralSettingsPage::retranslateUi); + retranslateUi(); +} + +QStringList GeneralSettingsPage::findQmFiles() +{ + QDir dir(translationPath); + QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name); + fileNames.replaceInStrings(QRegularExpression(translationPrefix + "_(.*)\\.qm"), "\\1"); + return fileNames; +} + +QString GeneralSettingsPage::languageName(const QString &lang) +{ + QTranslator qTranslator; + + QString appNameHint = translationPrefix + "_" + lang; + bool appTranslationLoaded = qTranslator.load(appNameHint, translationPath); + if (!appTranslationLoaded) { + qCWarning(GeneralSettingsPageLog) + << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath; + } + + return qTranslator.translate("i18n", DEFAULT_LANG_NAME); +} + +void GeneralSettingsPage::deckPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), deckPathEdit->text()); + if (path.isEmpty()) { + return; + } + + deckPathEdit->setText(path); + SettingsCache::instance().setDeckPath(path); +} + +void GeneralSettingsPage::filtersPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), filtersPathEdit->text()); + if (path.isEmpty()) { + return; + } + + filtersPathEdit->setText(path); + SettingsCache::instance().setFiltersPath(path); +} + +void GeneralSettingsPage::replaysPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text()); + if (path.isEmpty()) { + return; + } + + replaysPathEdit->setText(path); + SettingsCache::instance().setReplaysPath(path); +} + +void GeneralSettingsPage::picsPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), picsPathEdit->text()); + if (path.isEmpty()) { + return; + } + + picsPathEdit->setText(path); + SettingsCache::instance().setPicsPath(path); +} + +void GeneralSettingsPage::cardDatabasePathButtonClicked() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), cardDatabasePathEdit->text()); + if (path.isEmpty()) { + return; + } + + cardDatabasePathEdit->setText(path); + SettingsCache::instance().setCardDatabasePath(path); +} + +void GeneralSettingsPage::customCardDatabaseButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), customCardDatabasePathEdit->text()); + if (path.isEmpty()) { + return; + } + + customCardDatabasePathEdit->setText(path); + SettingsCache::instance().setCustomCardDatabasePath(path); +} + +void GeneralSettingsPage::tokenDatabasePathButtonClicked() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), tokenDatabasePathEdit->text()); + if (path.isEmpty()) { + return; + } + + tokenDatabasePathEdit->setText(path); + SettingsCache::instance().setTokenDatabasePath(path); +} + +void GeneralSettingsPage::resetAllPathsClicked() +{ + SettingsCache &settings = SettingsCache::instance(); + settings.resetPaths(); + deckPathEdit->setText(settings.getDeckPath()); + replaysPathEdit->setText(settings.getReplaysPath()); + picsPathEdit->setText(settings.getPicsPath()); + cardDatabasePathEdit->setText(settings.getCardDatabasePath()); + customCardDatabasePathEdit->setText(settings.getCustomCardDatabasePath()); + tokenDatabasePathEdit->setText(settings.getTokenDatabasePath()); + allPathsResetLabel->setVisible(true); +} + +void GeneralSettingsPage::languageBoxChanged(int index) +{ + SettingsCache::instance().setLang(languageBox.itemData(index).toString()); +} + +void GeneralSettingsPage::retranslateUi() +{ + languageGroupBox->setTitle(tr("Language settings")); + languageLabel.setText(tr("Language:")); + + versionGroupBox->setTitle(tr("Version settings")); + cardDatabaseGroupBox->setTitle(tr("Card database")); + startupGroupBox->setTitle(tr("Startup settings")); + + if (SettingsCache::instance().getIsPortableBuild()) { + pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)")); + } else { + pathsGroupBox->setTitle(tr("Paths")); + } + advertiseTranslationPageLabel.setText( + QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); + deckPathLabel.setText(tr("Decks directory:")); + filtersPathLabel.setText(tr("Filters directory:")); + replaysPathLabel.setText(tr("Replays directory:")); + picsPathLabel.setText(tr("Pictures directory:")); + cardDatabasePathLabel.setText(tr("Card database:")); + customCardDatabasePathLabel.setText(tr("Custom database directory:")); + tokenDatabasePathLabel.setText(tr("Token database:")); + updateReleaseChannelLabel.setText(tr("Update channel")); + startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup")); + startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup")); + startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check")); + startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt, + tr("Prompt for update")); + startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexAlways, + tr("Always update in the background")); + cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every")); + cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days")); + updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); + newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); + showTipsOnStartup.setText(tr("Show tips on startup")); + resetAllPathsButton->setText(tr("Reset all paths")); + + const auto &settings = SettingsCache::instance(); + + QDate lastCheckDate = settings.getLastCardUpdateCheck(); + int daysAgo = lastCheckDate.daysTo(QDate::currentDate()); + + lastCardUpdateCheckDateLabel.setText( + tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo)); + + // We can't change the strings after they're put into the QComboBox, so this is our workaround + int oldIndex = updateReleaseChannelBox.currentIndex(); + updateReleaseChannelBox.clear(); + for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) { + updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8())); + } + updateReleaseChannelBox.setCurrentIndex(oldIndex); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h new file mode 100644 index 000000000..8aa39ff65 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -0,0 +1,76 @@ +#ifndef COCKATRICE_GENERAL_SETTINGS_PAGE_H +#define COCKATRICE_GENERAL_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(GeneralSettingsPageLog, "general_settings_page"); + +class GeneralSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + GeneralSettingsPage(); + void retranslateUi() override; + +private slots: + void deckPathButtonClicked(); + void filtersPathButtonClicked(); + void replaysPathButtonClicked(); + void picsPathButtonClicked(); + void cardDatabasePathButtonClicked(); + void customCardDatabaseButtonClicked(); + void tokenDatabasePathButtonClicked(); + void resetAllPathsClicked(); + void languageBoxChanged(int index); + +private: + QStringList findQmFiles(); + QString languageName(const QString &lang); + + QGroupBox *languageGroupBox; + QGroupBox *versionGroupBox; + QGroupBox *cardDatabaseGroupBox; + QGroupBox *startupGroupBox; + QGroupBox *pathsGroupBox; + + QLineEdit *deckPathEdit; + QLineEdit *filtersPathEdit; + QLineEdit *replaysPathEdit; + QLineEdit *picsPathEdit; + QLineEdit *cardDatabasePathEdit; + QLineEdit *customCardDatabasePathEdit; + QLineEdit *tokenDatabasePathEdit; + QPushButton *resetAllPathsButton; + QLabel *allPathsResetLabel; + QComboBox languageBox; + QCheckBox startupUpdateCheckCheckBox; + QLabel startupCardUpdateCheckBehaviorLabel; + QComboBox startupCardUpdateCheckBehaviorSelector; + QLabel cardUpdateCheckIntervalLabel; + QSpinBox cardUpdateCheckIntervalSpinBox; + QLabel lastCardUpdateCheckDateLabel; + QCheckBox updateNotificationCheckBox; + QCheckBox newVersionOracleCheckBox; + QComboBox updateReleaseChannelBox; + QLabel languageLabel; + QLabel deckPathLabel; + QLabel filtersPathLabel; + QLabel replaysPathLabel; + QLabel picsPathLabel; + QLabel cardDatabasePathLabel; + QLabel customCardDatabasePathLabel; + QLabel tokenDatabasePathLabel; + QLabel updateReleaseChannelLabel; + QLabel advertiseTranslationPageLabel; + QCheckBox showTipsOnStartup; +}; + +#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp new file mode 100644 index 000000000..1e6f99245 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp @@ -0,0 +1,258 @@ +#include "messages_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../interface/widgets/utility/get_text_with_max.h" + +#include +#include +#include + +MessagesSettingsPage::MessagesSettingsPage() +{ + chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention()); + connect(&chatMentionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMention); + + chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter()); + connect(&chatMentionCompleterCheckbox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMentionCompleter); + + explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + explainMessagesLabel.setOpenExternalLinks(true); + + ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers()); + ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages()); + ignoreNonBuddyUserMessages.setChecked(SettingsCache::instance().getIgnoreNonBuddyUserMessages()); + + connect(&ignoreUnregUsersMainChat, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUsers); + connect(&ignoreUnregUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUserMessages); + connect(&ignoreNonBuddyUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreNonBuddyUserMessages); + + invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground()); + connect(&invertMentionForeground, &QCheckBox::QT_STATE_CHANGED, this, &MessagesSettingsPage::updateTextColor); + + invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground()); + connect(&invertHighlightForeground, &QCheckBox::QT_STATE_CHANGED, this, + &MessagesSettingsPage::updateTextHighlightColor); + + mentionColor = new QLineEdit(); + mentionColor->setText(SettingsCache::instance().getChatMentionColor()); + updateMentionPreview(); + connect(mentionColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateColor); + + messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); + connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMessagePopups); + + mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup()); + connect(&mentionPopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMentionPopups); + + roomHistory.setChecked(SettingsCache::instance().getRoomHistory()); + connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setRoomHistory); + + customAlertString = new QLineEdit(); + customAlertString->setText(SettingsCache::instance().getHighlightWords()); + connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance(), &SettingsCache::setHighlightWords); + + auto *chatGrid = new QGridLayout; + chatGrid->addWidget(&chatMentionCheckBox, 0, 0); + chatGrid->addWidget(&invertMentionForeground, 0, 1); + chatGrid->addWidget(mentionColor, 0, 2); + chatGrid->addWidget(&chatMentionCompleterCheckbox, 1, 0); + chatGrid->addWidget(&ignoreUnregUsersMainChat, 2, 0); + chatGrid->addWidget(&hexLabel, 1, 2); + chatGrid->addWidget(&ignoreUnregUserMessages, 3, 0); + chatGrid->addWidget(&ignoreNonBuddyUserMessages, 4, 0); + chatGrid->addWidget(&messagePopups, 5, 0); + chatGrid->addWidget(&mentionPopups, 6, 0); + chatGrid->addWidget(&roomHistory, 7, 0); + chatGroupBox = new QGroupBox; + chatGroupBox->setLayout(chatGrid); + + highlightColor = new QLineEdit(); + highlightColor->setText(SettingsCache::instance().getChatHighlightColor()); + updateHighlightPreview(); + connect(highlightColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateHighlightColor); + + auto *highlightNotice = new QGridLayout; + highlightNotice->addWidget(highlightColor, 0, 2); + highlightNotice->addWidget(&invertHighlightForeground, 0, 1); + highlightNotice->addWidget(&hexHighlightLabel, 1, 2); + highlightNotice->addWidget(customAlertString, 0, 0); + highlightNotice->addWidget(&customAlertStringLabel, 1, 0); + highlightGroupBox = new QGroupBox; + highlightGroupBox->setLayout(highlightNotice); + + messageList = new QListWidget; + + int count = SettingsCache::instance().messages().getCount(); + for (int i = 0; i < count; i++) { + messageList->addItem(SettingsCache::instance().messages().getMessageAt(i)); + } + + aAdd = new QAction(this); + aAdd->setIcon(QPixmap("theme:icons/increment")); + connect(aAdd, &QAction::triggered, this, &MessagesSettingsPage::actAdd); + + aEdit = new QAction(this); + aEdit->setIcon(QPixmap("theme:icons/pencil")); + connect(aEdit, &QAction::triggered, this, &MessagesSettingsPage::actEdit); + + aRemove = new QAction(this); + aRemove->setIcon(QPixmap("theme:icons/decrement")); + connect(aRemove, &QAction::triggered, this, &MessagesSettingsPage::actRemove); + + auto *messageToolBar = new QToolBar; + messageToolBar->setOrientation(Qt::Vertical); + messageToolBar->addAction(aAdd); + messageToolBar->addAction(aRemove); + messageToolBar->addAction(aEdit); + messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + + auto *messageListLayout = new QHBoxLayout; + messageListLayout->addWidget(messageToolBar); + messageListLayout->addWidget(messageList); + + auto *messagesLayout = new QVBoxLayout; // combines the explainer label with the actual messages widget pieces + messagesLayout->addLayout(messageListLayout); + messagesLayout->addWidget(&explainMessagesLabel); + + messageGroupBox = new QGroupBox; // draws a box around the above layout and allows it to be titled + messageGroupBox->setLayout(messagesLayout); + + auto *mainLayout = new QVBoxLayout; // combines the messages groupbox with the rest of the menu + mainLayout->addWidget(messageGroupBox); + mainLayout->addWidget(chatGroupBox); + mainLayout->addWidget(highlightGroupBox); + + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &MessagesSettingsPage::retranslateUi); + retranslateUi(); +} + +void MessagesSettingsPage::updateColor(const QString &value) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor colorToSet = QColor::fromString("#" + value); +#else + QColor colorToSet; + colorToSet.setNamedColor("#" + value); +#endif + if (colorToSet.isValid()) { + SettingsCache::instance().setChatMentionColor(value); + updateMentionPreview(); + } +} + +void MessagesSettingsPage::updateHighlightColor(const QString &value) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor colorToSet = QColor::fromString("#" + value); +#else + QColor colorToSet; + colorToSet.setNamedColor("#" + value); +#endif + if (colorToSet.isValid()) { + SettingsCache::instance().setChatHighlightColor(value); + updateHighlightPreview(); + } +} + +void MessagesSettingsPage::updateTextColor(QT_STATE_CHANGED_T value) +{ + SettingsCache::instance().setChatMentionForeground(value); + updateMentionPreview(); +} + +void MessagesSettingsPage::updateTextHighlightColor(QT_STATE_CHANGED_T value) +{ + SettingsCache::instance().setChatHighlightForeground(value); + updateHighlightPreview(); +} + +void MessagesSettingsPage::updateMentionPreview() +{ + mentionColor->setStyleSheet( + "QLineEdit{background:#" + SettingsCache::instance().getChatMentionColor() + + ";color: " + (SettingsCache::instance().getChatMentionForeground() ? "white" : "black") + ";}"); +} + +void MessagesSettingsPage::updateHighlightPreview() +{ + highlightColor->setStyleSheet( + "QLineEdit{background:#" + SettingsCache::instance().getChatHighlightColor() + + ";color: " + (SettingsCache::instance().getChatHighlightForeground() ? "white" : "black") + ";}"); +} + +void MessagesSettingsPage::storeSettings() +{ + SettingsCache::instance().messages().setCount(messageList->count()); + for (int i = 0; i < messageList->count(); i++) { + SettingsCache::instance().messages().setMessageAt(i, messageList->item(i)->text()); + } + emit SettingsCache::instance().messages().messageMacrosChanged(); +} + +void MessagesSettingsPage::actAdd() +{ + bool ok; + QString msg = + getTextWithMax(this, tr("Add message"), tr("Message:"), QLineEdit::Normal, QString(), &ok, MAX_TEXT_LENGTH); + if (ok) { + messageList->addItem(msg); + storeSettings(); + } +} + +void MessagesSettingsPage::actEdit() +{ + if (messageList->currentItem()) { + QString oldText = messageList->currentItem()->text(); + bool ok; + QString msg = + getTextWithMax(this, tr("Edit message"), tr("Message:"), QLineEdit::Normal, oldText, &ok, MAX_TEXT_LENGTH); + if (ok) { + messageList->currentItem()->setText(msg); + storeSettings(); + } + } +} + +void MessagesSettingsPage::actRemove() +{ + if (messageList->currentItem() != nullptr) { + delete messageList->takeItem(messageList->currentRow()); + storeSettings(); + } +} + +void MessagesSettingsPage::retranslateUi() +{ + chatGroupBox->setTitle(tr("Chat settings")); + highlightGroupBox->setTitle(tr("Custom alert words")); + chatMentionCheckBox.setText(tr("Enable chat mentions")); + chatMentionCompleterCheckbox.setText(tr("Enable mention completer")); + messageGroupBox->setTitle(tr("In-game message macros")); + explainMessagesLabel.setText( + QString("%2").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to use in-game message macros"))); + ignoreUnregUsersMainChat.setText(tr("Ignore chat room messages sent by unregistered users")); + ignoreUnregUserMessages.setText(tr("Ignore private messages sent by unregistered users")); + ignoreNonBuddyUserMessages.setText(tr("Ignore private messages sent by non-buddy users")); + invertMentionForeground.setText(tr("Invert text color")); + invertHighlightForeground.setText(tr("Invert text color")); + messagePopups.setText(tr("Enable desktop notifications for private messages")); + mentionPopups.setText(tr("Enable desktop notification for mentions")); + roomHistory.setText(tr("Enable room message history on join")); + hexLabel.setText(tr("(Color is hexadecimal)")); + hexHighlightLabel.setText(tr("(Color is hexadecimal)")); + customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only")); + customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); + aAdd->setText(tr("Add New Message")); + aEdit->setText(tr("Edit Message")); + aRemove->setText(tr("Remove Message")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h new file mode 100644 index 000000000..e98ae0592 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h @@ -0,0 +1,59 @@ +#ifndef COCKATRICE_MESSAGES_SETTINGS_PAGE_H +#define COCKATRICE_MESSAGES_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include + +class MessagesSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + MessagesSettingsPage(); + void retranslateUi() override; + +private slots: + void actAdd(); + void actEdit(); + void actRemove(); + void updateColor(const QString &value); + void updateHighlightColor(const QString &value); + void updateTextColor(QT_STATE_CHANGED_T value); + void updateTextHighlightColor(QT_STATE_CHANGED_T value); + +private: + QListWidget *messageList; + QAction *aAdd; + QAction *aEdit; + QAction *aRemove; + QCheckBox chatMentionCheckBox; + QCheckBox chatMentionCompleterCheckbox; + QCheckBox invertMentionForeground; + QCheckBox invertHighlightForeground; + QCheckBox ignoreUnregUsersMainChat; + QCheckBox ignoreUnregUserMessages; + QCheckBox ignoreNonBuddyUserMessages; + QCheckBox messagePopups; + QCheckBox mentionPopups; + QCheckBox roomHistory; + QGroupBox *chatGroupBox; + QGroupBox *highlightGroupBox; + QGroupBox *messageGroupBox; + QLineEdit *mentionColor; + QLineEdit *highlightColor; + QLineEdit *customAlertString; + QLabel hexLabel; + QLabel hexHighlightLabel; + QLabel customAlertStringLabel; + QLabel explainMessagesLabel; + + void storeSettings(); + void updateMentionPreview(); + void updateHighlightPreview(); +}; + +#endif // COCKATRICE_MESSAGES_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp new file mode 100644 index 000000000..e7a04ef79 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp @@ -0,0 +1,128 @@ +#include "shortcut_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../../../client/settings/shortcut_treeview.h" +#include "../interface/widgets/utility/custom_line_edit.h" +#include "../interface/widgets/utility/sequence_edit.h" + +#include +#include + +ShortcutSettingsPage::ShortcutSettingsPage() +{ + // search bar + searchEdit = new SearchLineEdit; + searchEdit->setObjectName("searchEdit"); + searchEdit->setClearButtonEnabled(true); + + setFocusProxy(searchEdit); + setFocusPolicy(Qt::ClickFocus); + + // table + shortcutsTable = new ShortcutTreeView(this); + + shortcutsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + shortcutsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + shortcutsTable->setColumnWidth(0, width() / 3 * 2); + searchEdit->setTreeView(shortcutsTable); + + connect(searchEdit, &SearchLineEdit::textChanged, shortcutsTable, &ShortcutTreeView::updateSearchString); + + // edit widget + currentActionGroupLabel = new QLabel(this); + currentActionGroupName = new QLabel(this); + currentActionLabel = new QLabel(this); + currentActionName = new QLabel(this); + currentShortcutLabel = new QLabel(this); + editTextBox = new SequenceEdit("", this); + shortcutsTable->installEventFilter(editTextBox); + + // buttons + faqLabel = new QLabel(this); + faqLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + faqLabel->setOpenExternalLinks(true); + + btnResetAll = new QPushButton(this); + btnClearAll = new QPushButton(this); + + btnResetAll->setIcon(QPixmap("theme:icons/update")); + btnClearAll->setIcon(QPixmap("theme:icons/clearsearch")); + + // layout + auto *_editLayout = new QGridLayout; + _editLayout->addWidget(currentActionGroupLabel, 0, 0); + _editLayout->addWidget(currentActionGroupName, 0, 1); + _editLayout->addWidget(currentActionLabel, 1, 0); + _editLayout->addWidget(currentActionName, 1, 1); + _editLayout->addWidget(currentShortcutLabel, 2, 0); + _editLayout->addWidget(editTextBox, 2, 1); + + editShortcutGroupBox = new QGroupBox; + editShortcutGroupBox->setLayout(_editLayout); + + auto *_buttonsLayout = new QHBoxLayout; + _buttonsLayout->addWidget(faqLabel); + _buttonsLayout->addWidget(btnResetAll); + _buttonsLayout->addWidget(btnClearAll); + + auto *_mainLayout = new QVBoxLayout; + _mainLayout->addWidget(searchEdit); + _mainLayout->addWidget(shortcutsTable); + _mainLayout->addWidget(editShortcutGroupBox); + _mainLayout->addLayout(_buttonsLayout); + + setLayout(_mainLayout); + + connect(btnResetAll, &QPushButton::clicked, this, &ShortcutSettingsPage::resetShortcuts); + connect(btnClearAll, &QPushButton::clicked, this, &ShortcutSettingsPage::clearShortcuts); + + connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &ShortcutSettingsPage::retranslateUi); + retranslateUi(); +} + +void ShortcutSettingsPage::currentItemChanged(const QString &key) +{ + if (key.isEmpty()) { + currentActionGroupName->setText(""); + currentActionName->setText(""); + editTextBox->setShortcutName(""); + } else { + QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); + QString action = SettingsCache::instance().shortcuts().getShortcut(key).getName(); + currentActionGroupName->setText(group); + currentActionName->setText(action); + editTextBox->setShortcutName(key); + } +} + +void ShortcutSettingsPage::resetShortcuts() +{ + if (QMessageBox::question(this, tr("Restore all default shortcuts"), + tr("Do you really want to restore all default shortcuts?")) == QMessageBox::Yes) { + SettingsCache::instance().shortcuts().resetAllShortcuts(); + } +} + +void ShortcutSettingsPage::clearShortcuts() +{ + if (QMessageBox::question(this, tr("Clear all default shortcuts"), + tr("Do you really want to clear all shortcuts?")) == QMessageBox::Yes) { + SettingsCache::instance().shortcuts().clearAllShortcuts(); + } +} + +void ShortcutSettingsPage::retranslateUi() +{ + shortcutsTable->retranslateUi(); + + currentActionGroupLabel->setText(tr("Section:")); + currentActionLabel->setText(tr("Action:")); + currentShortcutLabel->setText(tr("Shortcut:")); + editTextBox->retranslateUi(); + faqLabel->setText(QString("%2").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to set custom shortcuts"))); + btnResetAll->setText(tr("Restore all default shortcuts")); + btnClearAll->setText(tr("Clear all shortcuts")); + searchEdit->setPlaceholderText(tr("Search by shortcut name")); +} diff --git a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.h b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.h new file mode 100644 index 000000000..05391df77 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.h @@ -0,0 +1,45 @@ +#ifndef COCKATRICE_SHORTCUT_SETTINGS_PAGE_H +#define COCKATRICE_SHORTCUT_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include + +class SequenceEdit; +class ShortcutTreeView; +class SearchLineEdit; + +class ShortcutSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + ShortcutSettingsPage(); + void retranslateUi() override; + +private: + SearchLineEdit *searchEdit; + ShortcutTreeView *shortcutsTable; + QVBoxLayout *mainLayout; + QHBoxLayout *buttonsLayout; + QGroupBox *editShortcutGroupBox; + QGridLayout *editLayout; + QLabel *currentActionGroupLabel; + QLabel *currentActionGroupName; + QLabel *currentActionLabel; + QLabel *currentActionName; + QLabel *currentShortcutLabel; + SequenceEdit *editTextBox; + QLabel *faqLabel; + QPushButton *btnResetAll; + QPushButton *btnClearAll; + +private slots: + void resetShortcuts(); + void clearShortcuts(); + void currentItemChanged(const QString &key); +}; + +#endif // COCKATRICE_SHORTCUT_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/sound_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/sound_settings_page.cpp new file mode 100644 index 000000000..e7e92ea15 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/sound_settings_page.cpp @@ -0,0 +1,86 @@ +#include "sound_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../client/sound_engine.h" + +#include + +SoundSettingsPage::SoundSettingsPage() +{ + soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled()); + connect(&soundEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSoundEnabled); + + QString themeName = SettingsCache::instance().getSoundThemeName(); + + QStringList themeDirs = soundEngine->getAvailableThemes().keys(); + for (int i = 0; i < themeDirs.size(); i++) { + themeBox.addItem(themeDirs[i]); + if (themeDirs[i] == themeName) { + themeBox.setCurrentIndex(i); + } + } + + connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &SoundSettingsPage::themeBoxChanged); + connect(&soundTestButton, &QPushButton::clicked, soundEngine, &SoundEngine::testSound); + + masterVolumeSlider = new QSlider(Qt::Horizontal); + masterVolumeSlider->setMinimum(0); + masterVolumeSlider->setMaximum(100); + masterVolumeSlider->setValue(SettingsCache::instance().getMasterVolume()); + masterVolumeSlider->setToolTip(QString::number(SettingsCache::instance().getMasterVolume())); + connect(&SettingsCache::instance(), &SettingsCache::masterVolumeChanged, this, + &SoundSettingsPage::masterVolumeChanged); + connect(masterVolumeSlider, &QSlider::sliderReleased, soundEngine, &SoundEngine::testSound); + connect(masterVolumeSlider, &QSlider::valueChanged, &SettingsCache::instance(), &SettingsCache::setMasterVolume); + + masterVolumeSpinBox = new QSpinBox(); + masterVolumeSpinBox->setMinimum(0); + masterVolumeSpinBox->setMaximum(100); + masterVolumeSpinBox->setValue(SettingsCache::instance().getMasterVolume()); + connect(masterVolumeSlider, &QSlider::valueChanged, masterVolumeSpinBox, &QSpinBox::setValue); + connect(masterVolumeSpinBox, qOverload(&QSpinBox::valueChanged), masterVolumeSlider, &QSlider::setValue); + + auto *soundGrid = new QGridLayout; + soundGrid->addWidget(&soundEnabledCheckBox, 0, 0, 1, 3); + soundGrid->addWidget(&masterVolumeLabel, 1, 0); + soundGrid->addWidget(masterVolumeSlider, 1, 1); + soundGrid->addWidget(masterVolumeSpinBox, 1, 2); + soundGrid->addWidget(&themeLabel, 2, 0); + soundGrid->addWidget(&themeBox, 2, 1); + soundGrid->addWidget(&soundTestButton, 3, 1); + + soundGroupBox = new QGroupBox; + soundGroupBox->setLayout(soundGrid); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(soundGroupBox); + mainLayout->addStretch(); + + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &SoundSettingsPage::retranslateUi); + retranslateUi(); +} + +void SoundSettingsPage::themeBoxChanged(int index) +{ + QStringList themeDirs = soundEngine->getAvailableThemes().keys(); + if (index >= 0 && index < themeDirs.count()) { + SettingsCache::instance().setSoundThemeName(themeDirs.at(index)); + } +} + +void SoundSettingsPage::masterVolumeChanged(int value) +{ + masterVolumeSlider->setToolTip(QString::number(value)); +} + +void SoundSettingsPage::retranslateUi() +{ + soundEnabledCheckBox.setText(tr("Enable &sounds")); + themeLabel.setText(tr("Current sounds theme:")); + soundTestButton.setText(tr("Test system sound engine")); + soundGroupBox->setTitle(tr("Sound settings")); + masterVolumeLabel.setText(tr("Master volume")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/sound_settings_page.h b/cockatrice/src/interface/widgets/settings_page/sound_settings_page.h new file mode 100644 index 000000000..f90bcbc5a --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/sound_settings_page.h @@ -0,0 +1,35 @@ +#ifndef COCKATRICE_SOUND_SETTINGS_PAGE_H +#define COCKATRICE_SOUND_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include +#include + +class SoundSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + SoundSettingsPage(); + void retranslateUi() override; + +private: + QLabel themeLabel; + QComboBox themeBox; + QGroupBox *soundGroupBox; + QPushButton soundTestButton; + QCheckBox soundEnabledCheckBox; + QLabel masterVolumeLabel; + QSlider *masterVolumeSlider; + QSpinBox *masterVolumeSpinBox; + +private slots: + void masterVolumeChanged(int value); + void themeBoxChanged(int index); +}; + +#endif // COCKATRICE_SOUND_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/storage_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/storage_settings_page.cpp new file mode 100644 index 000000000..c9d3c7789 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/storage_settings_page.cpp @@ -0,0 +1,244 @@ +#include "storage_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../interface/card_picture_loader/card_picture_loader.h" + +#include +#include +#include + +StorageSettingsPage::StorageSettingsPage() +{ + auto *lpNetworkCacheGrid = new QGridLayout; + auto *lpImageBackupGrid = new QGridLayout; + auto *lpPixmapCacheGrid = new QGridLayout; + + networkCacheExplainerLabel.setWordWrap(true); + imageBackupExplainerLabel.setWordWrap(true); + pixmapCacheExplainerLabel.setWordWrap(true); + + connect(&clearDownloadedPicsButton, &QPushButton::clicked, this, + &StorageSettingsPage::clearDownloadedPicsButtonClicked); + + connect(&clearPixmapCacheButton, &QPushButton::clicked, this, &StorageSettingsPage::clearPixmapCacheButtonClicked); + + // pixmap cache + pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); + // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size) + pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX); + pixmapCacheEdit.setSingleStep(64); + pixmapCacheEdit.setValue(SettingsCache::instance().getPixmapCacheSize()); + pixmapCacheEdit.setSuffix(" MB"); + + // Caching method + + cardPictureLoaderCacheMethodComboBox = new QComboBox; + for (auto method : CardPictureLoaderCacheMethod::methods()) { + cardPictureLoaderCacheMethodComboBox->addItem(method.displayName, static_cast(method.id)); + } + + int currentCacheMethod = static_cast(SettingsCache::instance().getCardPictureLoaderCacheMethod()); + + int currentIndex = cardPictureLoaderCacheMethodComboBox->findData(currentCacheMethod); + if (currentIndex >= 0) { + cardPictureLoaderCacheMethodComboBox->setCurrentIndex(currentIndex); + } + + connect(cardPictureLoaderCacheMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + auto cacheMethod = static_cast( + cardPictureLoaderCacheMethodComboBox->itemData(index).toInt()); + + bool useNetworkCache = (cacheMethod == CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE); + + if (useNetworkCache) { + clearImageBackupsButtonClicked(); + } else { + clearDownloadedPicsButtonClicked(); + } + + mpNetworkCacheGroupBox->setEnabled(useNetworkCache); + mpImageBackupGroupBox->setEnabled(!useNetworkCache); + + SettingsCache::instance().setCardImageCacheMethod(cacheMethod); + }); + + // Network Cache + + networkCacheEdit.setMinimum(NETWORK_CACHE_SIZE_MIN); + networkCacheEdit.setMaximum(NETWORK_CACHE_SIZE_MAX); + networkCacheEdit.setSingleStep(1); + networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB()); + networkCacheEdit.setSuffix(" MB"); + + networkRedirectCacheTtlEdit.setMinimum(NETWORK_REDIRECT_CACHE_TTL_MIN); + networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX); + networkRedirectCacheTtlEdit.setSingleStep(1); + networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl()); + + // Image Backup + localCardImageStorageNamingSchemeComboBox = new QComboBox; + for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) { + localCardImageStorageNamingSchemeComboBox->addItem(scheme.displayName, static_cast(scheme.id)); + } + + int current = static_cast(SettingsCache::instance().getLocalCardImageStorageNamingScheme()); + + int index = localCardImageStorageNamingSchemeComboBox->findData(current); + if (index >= 0) { + localCardImageStorageNamingSchemeComboBox->setCurrentIndex(index); + } + + connect(localCardImageStorageNamingSchemeComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + auto scheme = static_cast( + localCardImageStorageNamingSchemeComboBox->itemData(index).toInt()); + SettingsCache::instance().setLocalCardImageStorageNamingScheme(scheme); + }); + + connect(&clearBackupsButton, &QPushButton::clicked, this, &StorageSettingsPage::clearImageBackupsButtonClicked); + + auto cacheMethodLayout = new QHBoxLayout; + cacheMethodLayout->addWidget(&cardPictureLoaderCacheMethodLabel); + cacheMethodLayout->addWidget(cardPictureLoaderCacheMethodComboBox); + + auto networkCacheLayout = new QHBoxLayout; + networkCacheLayout->addWidget(&clearDownloadedPicsButton); + networkCacheLayout->addStretch(); + networkCacheLayout->addWidget(&networkCacheLabel); + networkCacheLayout->addWidget(&networkCacheEdit); + + auto networkRedirectCacheLayout = new QHBoxLayout; + networkRedirectCacheLayout->addStretch(); + networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel); + networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit); + + auto pixmapCacheLayout = new QHBoxLayout; + pixmapCacheLayout->addWidget(&clearPixmapCacheButton); + pixmapCacheLayout->addStretch(); + pixmapCacheLayout->addWidget(&pixmapCacheLabel); + pixmapCacheLayout->addWidget(&pixmapCacheEdit); + + lpNetworkCacheGrid->addWidget(&networkCacheExplainerLabel, 0, 0); + lpNetworkCacheGrid->addLayout(networkCacheLayout, 1, 0); + lpNetworkCacheGrid->addLayout(networkRedirectCacheLayout, 2, 0); + + // Image Backup Layout + lpImageBackupGrid->addWidget(&imageBackupExplainerLabel, 0, 0, 1, 2); + lpImageBackupGrid->addWidget(&localCardImageStorageNamingSchemeLabel, 1, 0); + lpImageBackupGrid->addWidget(localCardImageStorageNamingSchemeComboBox, 1, 1); + lpImageBackupGrid->addWidget(&clearBackupsButton, 2, 0); + + lpPixmapCacheGrid->addWidget(&pixmapCacheExplainerLabel, 0, 0); + lpPixmapCacheGrid->addLayout(pixmapCacheLayout, 1, 0); + + connect(&pixmapCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setPixmapCacheSize); + connect(&networkCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setNetworkCacheSizeInMB); + connect(&networkRedirectCacheTtlEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setNetworkRedirectCacheTtl); + + mpCacheMethodGroupBox = new QGroupBox; + mpCacheMethodGroupBox->setLayout(cacheMethodLayout); + + mpNetworkCacheGroupBox = new QGroupBox; + mpNetworkCacheGroupBox->setLayout(lpNetworkCacheGrid); + + mpImageBackupGroupBox = new QGroupBox; + mpImageBackupGroupBox->setLayout(lpImageBackupGrid); + + mpPixmapCacheGroupBox = new QGroupBox; + mpPixmapCacheGroupBox->setLayout(lpPixmapCacheGrid); + + auto *lpMainLayout = new QVBoxLayout; + + lpMainLayout->addWidget(mpCacheMethodGroupBox); + lpMainLayout->addWidget(mpNetworkCacheGroupBox); + lpMainLayout->addWidget(mpImageBackupGroupBox); + lpMainLayout->addWidget(mpPixmapCacheGroupBox); + lpMainLayout->addStretch(); + + setLayout(lpMainLayout); + + bool useNetworkCache = SettingsCache::instance().getCardPictureLoaderCacheMethod() == + CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE; + + mpNetworkCacheGroupBox->setEnabled(useNetworkCache); + mpImageBackupGroupBox->setEnabled(!useNetworkCache); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &StorageSettingsPage::retranslateUi); + retranslateUi(); +} + +void StorageSettingsPage::clearDownloadedPicsButtonClicked() +{ + CardPictureLoader::clearNetworkCache(); + CardPictureLoader::clearPixmapCache(); + QMessageBox::information(this, tr("Success"), tr("Cached card pictures have been reset.")); +} + +void StorageSettingsPage::clearImageBackupsButtonClicked() +{ + QString picsPath = SettingsCache::instance().getPicsPath() + "/downloadedPics"; + + QDir dir(picsPath); + bool success = dir.removeRecursively(); + + CardPictureLoader::clearPixmapCache(); + + if (success) { + QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); + } else { + QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); + } +} + +void StorageSettingsPage::clearPixmapCacheButtonClicked() +{ + CardPictureLoader::clearPixmapCache(); + QMessageBox::information(this, tr("Success"), tr("In-memory (currently loaded) card pictures have been reset.")); +} + +void StorageSettingsPage::retranslateUi() +{ + cardPictureLoaderCacheMethodLabel.setText(tr("Card Picture Loader Caching Method:")); + + networkCacheExplainerLabel.setText( + tr("The network cache is the preferred way of storing images. Downloaded images " + "are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors " + "this cache and deletes the least recently seen card images to ensure the cache does not exceed the " + "configured size.")); + imageBackupExplainerLabel.setText( + tr("Writing card images directly to a folder on your hard drive is another way " + "of storing images. This does not change how Cockatrice accesses or downloads " + "images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it " + "is up to you to ensure sufficient available space. It should also be noted that if a provider outage " + "causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you " + "manually delete the file, as opposed to using the network cache, which automatically rotates and thus " + "correct errors after a while.")); + pixmapCacheExplainerLabel.setText( + tr("This is the in-memory picture cache used by the application at runtime. It determines how much memory " + "(RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will " + "allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make " + "Cockatrice reload all images from the network cache or the disk.")); + + clearDownloadedPicsButton.setText(tr("Delete Cached Images")); + clearBackupsButton.setText(tr("Delete Saved Images")); + clearPixmapCacheButton.setText(tr("Clear In-Memory Images")); + + mpCacheMethodGroupBox->setTitle(tr("Card Picture Loader Cache Method")); + mpNetworkCacheGroupBox->setTitle(tr("Network Cache")); + mpImageBackupGroupBox->setTitle(tr("Filesystem")); + mpPixmapCacheGroupBox->setTitle(tr("In-Memory Picture Cache")); + + networkCacheLabel.setText(tr("Network Cache Size:")); + networkCacheEdit.setToolTip(tr("On-disk cache for downloaded pictures")); + networkRedirectCacheTtlLabel.setText(tr("Redirect Cache TTL:")); + networkRedirectCacheTtlEdit.setToolTip(tr("How long cached redirects for urls are valid for.")); + pixmapCacheLabel.setText(tr("Picture Cache Size:")); + pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen")); + localCardImageStorageNamingSchemeLabel.setText(tr("Naming scheme:")); + + networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/storage_settings_page.h b/cockatrice/src/interface/widgets/settings_page/storage_settings_page.h new file mode 100644 index 000000000..5c8a00981 --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/storage_settings_page.h @@ -0,0 +1,50 @@ +#ifndef COCKATRICE_STORAGE_SETTINGS_PAGE_H +#define COCKATRICE_STORAGE_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include + +class StorageSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +public: + StorageSettingsPage(); + void retranslateUi() override; + +private slots: + void clearDownloadedPicsButtonClicked(); + void clearImageBackupsButtonClicked(); + void clearPixmapCacheButtonClicked(); + +private: + QPushButton clearDownloadedPicsButton; + QPushButton clearBackupsButton; + QPushButton clearPixmapCacheButton; + + QGroupBox *mpCacheMethodGroupBox; + QGroupBox *mpNetworkCacheGroupBox; + QGroupBox *mpImageBackupGroupBox; + QGroupBox *mpPixmapCacheGroupBox; + + QLabel networkCacheExplainerLabel; + QLabel imageBackupExplainerLabel; + QLabel pixmapCacheExplainerLabel; + + QLabel cardPictureLoaderCacheMethodLabel; + QComboBox *cardPictureLoaderCacheMethodComboBox; + QLabel networkCacheLabel; + QSpinBox networkCacheEdit; + QLabel networkRedirectCacheTtlLabel; + QSpinBox networkRedirectCacheTtlEdit; + QSpinBox pixmapCacheEdit; + QLabel pixmapCacheLabel; + QLabel localCardImageStorageNamingSchemeLabel; + QComboBox *localCardImageStorageNamingSchemeComboBox; +}; + +#endif // COCKATRICE_STORAGE_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp new file mode 100644 index 000000000..dfa736a1a --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -0,0 +1,234 @@ +#include "user_interface_settings_page.h" + +#include "../../../client/settings/cache_settings.h" +#include "../interface/widgets/tabs/tab_supervisor.h" + +#include + +enum visualDeckStoragePromptForConversionIndex +{ + visualDeckStoragePromptForConversionIndexNone, + visualDeckStoragePromptForConversionIndexPrompt, + visualDeckStoragePromptForConversionIndexAlways +}; + +UserInterfaceSettingsPage::UserInterfaceSettingsPage() +{ + // general settings and notification settings + notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled()); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setNotificationsEnabled); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &UserInterfaceSettingsPage::setNotificationEnabled); + + specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled()); + specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); + connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSpectatorNotificationsEnabled); + + buddyConnectNotificationsEnabledCheckBox.setChecked( + SettingsCache::instance().getBuddyConnectNotificationsEnabled()); + buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); + connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setBuddyConnectNotificationsEnabled); + + doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay()); + connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoubleClickToPlay); + + clickPlaysAllSelectedCheckBox.setChecked(SettingsCache::instance().getClickPlaysAllSelected()); + connect(&clickPlaysAllSelectedCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setClickPlaysAllSelected); + + playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); + connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPlayToStack); + + doNotDeleteArrowsInSubPhasesCheckBox.setChecked(SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()); + connect(&doNotDeleteArrowsInSubPhasesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoNotDeleteArrowsInSubPhases); + + closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); + connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setCloseEmptyCardView); + + focusCardViewSearchBarCheckBox.setChecked(SettingsCache::instance().getFocusCardViewSearchBar()); + connect(&focusCardViewSearchBarCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setFocusCardViewSearchBar); + + annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); + connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setAnnotateTokens); + + showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); + connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowDragSelectionCount); + + showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); + connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowTotalSelectionCount); + + useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); + connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); + + auto *generalGrid = new QGridLayout; + generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); + generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); + generalGrid->addWidget(&playToStackCheckBox, 2, 0); + generalGrid->addWidget(&doNotDeleteArrowsInSubPhasesCheckBox, 3, 0); + generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); + generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); + generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); + generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); + + generalGroupBox = new QGroupBox; + generalGroupBox->setLayout(generalGrid); + + auto *notificationsGrid = new QGridLayout; + notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0); + notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0); + notificationsGrid->addWidget(&buddyConnectNotificationsEnabledCheckBox, 2, 0); + + notificationsGroupBox = new QGroupBox; + notificationsGroupBox->setLayout(notificationsGrid); + + // animation settings + tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation()); + connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setTapAnimation); + + auto *animationGrid = new QGridLayout; + animationGrid->addWidget(&tapAnimationCheckBox, 0, 0); + + animationGroupBox = new QGroupBox; + animationGroupBox->setLayout(animationGrid); + + // deck editor settings + openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab()); + connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setOpenDeckInNewTab); + + visualDeckStorageInGameCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageInGame()); + connect(&visualDeckStorageInGameCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageInGame); + + visualDeckStorageSelectionAnimationCheckBox.setChecked( + SettingsCache::instance().getVisualDeckStorageSelectionAnimation()); + connect(&visualDeckStorageSelectionAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageSelectionAnimation); + + visualDeckStoragePromptForConversionSelector.addItem(""); // these will be set in retranslateUI + visualDeckStoragePromptForConversionSelector.addItem(""); + visualDeckStoragePromptForConversionSelector.addItem(""); + if (SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexPrompt); + } else if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexAlways); + } else { + visualDeckStoragePromptForConversionSelector.setCurrentIndex(visualDeckStoragePromptForConversionIndexNone); + } + connect(&visualDeckStoragePromptForConversionSelector, QOverload::of(&QComboBox::currentIndexChanged), this, + [](int index) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion( + index == visualDeckStoragePromptForConversionIndexPrompt); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert( + index == visualDeckStoragePromptForConversionIndexAlways); + }); + + defaultDeckEditorTypeSelector.addItem(""); // these will be set in retranslateUI + defaultDeckEditorTypeSelector.addItem(""); + defaultDeckEditorTypeSelector.setCurrentIndex(SettingsCache::instance().getDefaultDeckEditorType()); + connect(&defaultDeckEditorTypeSelector, QOverload::of(&QComboBox::currentIndexChanged), + &SettingsCache::instance(), &SettingsCache::setDefaultDeckEditorType); + + auto *deckEditorGrid = new QGridLayout; + deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); + deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0); + deckEditorGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 2, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 3, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); + deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0); + deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1); + + deckEditorGroupBox = new QGroupBox; + deckEditorGroupBox->setLayout(deckEditorGrid); + + // replay settings + rewindBufferingMsBox.setRange(0, 9999); + rewindBufferingMsBox.setValue(SettingsCache::instance().getRewindBufferingMs()); + connect(&rewindBufferingMsBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setRewindBufferingMs); + + auto *replayGrid = new QGridLayout; + replayGrid->addWidget(&rewindBufferingMsLabel, 0, 0, 1, 1); + replayGrid->addWidget(&rewindBufferingMsBox, 0, 1, 1, 1); + + replayGroupBox = new QGroupBox; + replayGroupBox->setLayout(replayGrid); + + // putting it all together + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + mainLayout->addWidget(notificationsGroupBox); + mainLayout->addWidget(animationGroupBox); + mainLayout->addWidget(deckEditorGroupBox); + mainLayout->addWidget(replayGroupBox); + mainLayout->addStretch(); + + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &UserInterfaceSettingsPage::retranslateUi); + retranslateUi(); +} + +void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) +{ + specNotificationsEnabledCheckBox.setEnabled(i != 0); + buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0); + if (i == 0) { + specNotificationsEnabledCheckBox.setChecked(false); + buddyConnectNotificationsEnabledCheckBox.setChecked(false); + } +} + +void UserInterfaceSettingsPage::retranslateUi() +{ + generalGroupBox->setTitle(tr("General interface settings")); + doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); + clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); + playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + doNotDeleteArrowsInSubPhasesCheckBox.setText(tr("Do not delete &arrows inside of subphases")); + closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); + focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); + annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); + showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); + showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); + useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); + notificationsGroupBox->setTitle(tr("Notifications settings")); + notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); + specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating")); + buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect")); + animationGroupBox->setTitle(tr("Animation settings")); + tapAnimationCheckBox.setText(tr("&Tap/untap animation")); + deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); + openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); + visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby")); + visualDeckStorageSelectionAnimationCheckBox.setText(tr("Use selection animation for Visual Deck Storage")); + visualDeckStoragePromptForConversionLabel.setText( + tr("When adding a tag in the visual deck storage to a .txt deck:")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexNone, + tr("do nothing")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexPrompt, + tr("ask to convert to .cod")); + visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexAlways, + tr("always convert to .cod")); + defaultDeckEditorTypeLabel.setText(tr("Default deck editor type")); + defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor")); + defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor")); + replayGroupBox->setTitle(tr("Replay settings")); + rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); + rewindBufferingMsBox.setSuffix(" ms"); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h new file mode 100644 index 000000000..6dd43ceae --- /dev/null +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -0,0 +1,54 @@ +#ifndef COCKATRICE_USER_INTERFACE_SETTINGS_PAGE_H +#define COCKATRICE_USER_INTERFACE_SETTINGS_PAGE_H + +#include "abstract_settings_page.h" + +#include +#include +#include +#include +#include +#include + +class UserInterfaceSettingsPage : public AbstractSettingsPage +{ + Q_OBJECT +private slots: + void setNotificationEnabled(QT_STATE_CHANGED_T); + +private: + QCheckBox notificationsEnabledCheckBox; + QCheckBox specNotificationsEnabledCheckBox; + QCheckBox buddyConnectNotificationsEnabledCheckBox; + QCheckBox doubleClickToPlayCheckBox; + QCheckBox clickPlaysAllSelectedCheckBox; + QCheckBox playToStackCheckBox; + QCheckBox doNotDeleteArrowsInSubPhasesCheckBox; + QCheckBox closeEmptyCardViewCheckBox; + QCheckBox focusCardViewSearchBarCheckBox; + QCheckBox annotateTokensCheckBox; + QCheckBox showDragSelectionCountCheckBox; + QCheckBox showTotalSelectionCountCheckBox; + QCheckBox useTearOffMenusCheckBox; + QCheckBox tapAnimationCheckBox; + QCheckBox openDeckInNewTabCheckBox; + QLabel visualDeckStoragePromptForConversionLabel; + QComboBox visualDeckStoragePromptForConversionSelector; + QCheckBox visualDeckStorageInGameCheckBox; + QCheckBox visualDeckStorageSelectionAnimationCheckBox; + QLabel defaultDeckEditorTypeLabel; + QComboBox defaultDeckEditorTypeSelector; + QLabel rewindBufferingMsLabel; + QSpinBox rewindBufferingMsBox; + QGroupBox *generalGroupBox; + QGroupBox *notificationsGroupBox; + QGroupBox *animationGroupBox; + QGroupBox *deckEditorGroupBox; + QGroupBox *replayGroupBox; + +public: + UserInterfaceSettingsPage(); + void retranslateUi() override; +}; + +#endif // COCKATRICE_USER_INTERFACE_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp index afc834e10..a8cc4cee6 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -56,6 +56,9 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta deckStateManager = new DeckStateManager(this); + databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); + databaseModel->setObjectName("databaseModel"); + cardDatabaseDockWidget = new DeckEditorCardDatabaseDockWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this); @@ -105,16 +108,17 @@ void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *wi dockToActions.insert(widget, {menu, aVisible, aFloating, defaultSize}); } -/** - * @brief Updates the card info dock and printing selector. - * @param card The card to display. - */ void AbstractTabDeckEditor::updateCard(const ExactCard &card) { cardInfoDockWidget->updateCard(card); printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr()); } +void AbstractTabDeckEditor::updateCardInfo(const ExactCard &card) +{ + cardInfoDockWidget->updateCard(card); +} + /** @brief Placeholder: called when the deck changes. */ void AbstractTabDeckEditor::onDeckChanged() { @@ -129,48 +133,14 @@ void AbstractTabDeckEditor::onDeckModified() emit tabTextChanged(this, getTabText()); } -/** - * @brief Helper for adding a card to a deck zone. - * @param card Card to add. - * @param zoneName Zone to add the card to. - */ -void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName) +void AbstractTabDeckEditor::addCard(const ExactCard &card, const QString &zoneName) { deckStateManager->addCard(card, zoneName); - - cardDatabaseDockWidget->highlightAllSearchEdit(); } -/** - * @brief Adds a card to the main deck or sideboard depending on Ctrl key. - */ -void AbstractTabDeckEditor::actAddCard(const ExactCard &card) +void AbstractTabDeckEditor::decrementCard(const ExactCard &card, const QString &zoneName) { - if (QApplication::keyboardModifiers() & Qt::ControlModifier) - actAddCardToSideboard(card); - else - addCardHelper(card, DECK_ZONE_MAIN); - - deckMenu->setSaveStatus(true); -} - -/** @brief Adds a card to the sideboard explicitly. */ -void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card) -{ - addCardHelper(card, DECK_ZONE_SIDE); - deckMenu->setSaveStatus(true); -} - -/** @brief Decrements a card from the main deck. */ -void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card) -{ - deckStateManager->decrementCard(card, DECK_ZONE_MAIN); -} - -/** @brief Decrements a card from the sideboard. */ -void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) -{ - deckStateManager->decrementCard(card, DECK_ZONE_SIDE); + deckStateManager->decrementCard(card, zoneName); } /** @@ -203,8 +173,9 @@ void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) void AbstractTabDeckEditor::actNewDeck() { auto deckOpenLocation = confirmOpen(false); - if (deckOpenLocation == CANCELLED) + if (deckOpenLocation == CANCELLED) { return; + } if (deckOpenLocation == NEW_TAB) { emit openDeckEditor(LoadedDeck()); @@ -229,22 +200,25 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified() AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) { if (SettingsCache::instance().getOpenDeckInNewTab()) { - if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) + if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) { return SAME_TAB; - else + } else { return NEW_TAB; + } } - if (!deckStateManager->isModified()) + if (!deckStateManager->isModified()) { return SAME_TAB; + } tabSupervisor->setCurrentWidget(this); QMessageBox *msgBox = createSaveConfirmationWindow(); QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole); int ret = msgBox->exec(); - if (msgBox->clickedButton() == newTabButton) + if (msgBox->clickedButton() == newTabButton) { return NEW_TAB; + } switch (ret) { case QMessageBox::Save: @@ -277,12 +251,14 @@ QMessageBox *AbstractTabDeckEditor::createSaveConfirmationWindow() void AbstractTabDeckEditor::actLoadDeck() { auto deckOpenLocation = confirmOpen(); - if (deckOpenLocation == CANCELLED) + if (deckOpenLocation == CANCELLED) { return; + } DlgLoadDeck dialog(this); - if (!dialog.exec()) + if (!dialog.exec()) { return; + } QString fileName = dialog.selectedFiles().at(0); openDeckFromFile(fileName, deckOpenLocation); @@ -295,8 +271,9 @@ void AbstractTabDeckEditor::actLoadDeck() void AbstractTabDeckEditor::actOpenRecent(const QString &fileName) { auto deckOpenLocation = confirmOpen(); - if (deckOpenLocation == CANCELLED) + if (deckOpenLocation == CANCELLED) { return; + } openDeckFromFile(fileName, deckOpenLocation); } @@ -349,8 +326,9 @@ bool AbstractTabDeckEditor::actSaveDeck() return true; } - if (loadedDeck.lastLoadInfo.fileName.isEmpty()) + if (loadedDeck.lastLoadInfo.fileName.isEmpty()) { return actSaveDeckAs(); + } if (DeckLoader::saveToFile(loadedDeck)) { deckStateManager->setModified(false); @@ -378,8 +356,9 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); dialog.selectFile(deckList.getName().trimmed()); - if (!dialog.exec()) + if (!dialog.exec()) { return false; + } QString fileName = dialog.selectedFiles().at(0); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); @@ -405,10 +384,11 @@ bool AbstractTabDeckEditor::actSaveDeckAs() */ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response) { - if (response.response_code() != Response::RespOk) + if (response.response_code() != Response::RespOk) { QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); - else + } else { deckStateManager->setModified(false); + } } /** @@ -418,12 +398,14 @@ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response) void AbstractTabDeckEditor::actLoadDeckFromClipboard() { auto deckOpenLocation = confirmOpen(); - if (deckOpenLocation == CANCELLED) + if (deckOpenLocation == CANCELLED) { return; + } DlgLoadDeckFromClipboard dlg(this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } if (deckOpenLocation == NEW_TAB) { emit openDeckEditor({.deckList = dlg.getDeckList()}); @@ -443,8 +425,9 @@ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); DlgEditDeckInClipboard dlg(loadedDeck.deckList, annotated, this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } setDeck({dlg.getDeckList(), loadedDeck.lastLoadInfo}); deckStateManager->setModified(true); @@ -502,12 +485,14 @@ void AbstractTabDeckEditor::actPrintDeck() void AbstractTabDeckEditor::actLoadDeckFromWebsite() { auto deckOpenLocation = confirmOpen(); - if (deckOpenLocation == CANCELLED) + if (deckOpenLocation == CANCELLED) { return; + } DlgLoadDeckFromWebsite dlg(this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } if (deckOpenLocation == NEW_TAB) { emit openDeckEditor({.deckList = dlg.getDeck()}); @@ -557,14 +542,14 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz() /** @brief Analyzes the deck using DeckStats. */ void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { - auto *interface = new DeckStatsInterface(*cardDatabaseDockWidget->getDatabase(), this); + auto *interface = new DeckStatsInterface(this); interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Analyzes the deck using TappedOut. */ void AbstractTabDeckEditor::actAnalyzeDeckTappedout() { - auto *interface = new TappedOutInterface(*cardDatabaseDockWidget->getDatabase(), this); + auto *interface = new TappedOutInterface(this); interface->analyzeDeck(deckStateManager->getDeckList()); } @@ -590,10 +575,11 @@ bool AbstractTabDeckEditor::confirmClose() if (deckStateManager->isModified()) { tabSupervisor->setCurrentWidget(this); int ret = createSaveConfirmationWindow()->exec(); - if (ret == QMessageBox::Save) + if (ret == QMessageBox::Save) { return actSaveDeck(); - else if (ret == QMessageBox::Cancel) + } else if (ret == QMessageBox::Cancel) { return false; + } } return true; } @@ -601,7 +587,20 @@ bool AbstractTabDeckEditor::confirmClose() /** @brief Handles close requests from outside (tab manager). */ bool AbstractTabDeckEditor::closeRequest() { - if (!confirmClose()) + if (!confirmClose()) { return false; + } return close(); } + +void AbstractTabDeckEditor::showPrintingSelector() +{ + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); + printingSelectorDockWidget->printingSelector->updateDisplay(); + printingSelectorDockWidget->setVisible(true); +} + +void AbstractTabDeckEditor::openEdhrecTab(const CardInfoPtr &info, bool isCommander) +{ + getTabSupervisor()->addEdhrecTab(info, isCommander); +} diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 477c3f973..34c585597 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -77,8 +77,8 @@ class QAction; * * **Key Methods:** * - * - actAddCard(const ExactCard &card) — Adds a card to the deck. - * - actDecrementCard(const ExactCard &card) — Removes a single instance of a card from the deck. + * - addCard(const ExactCard &card, const QString &zoneName) — Adds a card to the deck. + * - decrementCard(const ExactCard &card, const QString &zoneName) — Removes a single instance of a card from the deck. * - actRemoveCard() — Removes the currently selected card from the deck. * - actSaveDeckAs() — Performs a "Save As" action for the deck. * - updateCard(const ExactCard &card) — Updates the currently displayed card info in the dock. @@ -126,6 +126,7 @@ public: // UI Elements DeckStateManager *deckStateManager; + CardDatabaseModel *databaseModel; ///< Card database DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorCardDatabaseDockWidget *cardDatabaseDockWidget; ///< Database dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock @@ -140,22 +141,35 @@ public slots: /** @brief Called when the deck is modified. */ virtual void onDeckModified(); - /** @brief Updates the card info panel. - * @param card The card to display. + /** + * @brief Updates the card info dock and printing selector. + * @param card The card to display. */ void updateCard(const ExactCard &card); - /** @brief Adds a card to the main deck or sideboard based on Ctrl key. */ - void actAddCard(const ExactCard &card); + /** + * @brief Updates just the card info dock + * @param card The card to display + */ + void updateCardInfo(const ExactCard &card); - /** @brief Adds a card to the sideboard explicitly. */ - void actAddCardToSideboard(const ExactCard &card); + /** + * @brief Adds a card to the given zone + * @param card Card to add. + * @param zoneName Zone to add the card to. + */ + void addCard(const ExactCard &card, const QString &zoneName); - /** @brief Decrements a card from the main deck. */ - void actDecrementCard(const ExactCard &card); - - /** @brief Decrements a card from the sideboard. */ - void actDecrementCardFromSideboard(const ExactCard &card); + /** + * @brief Decrements a card from the given zone + * + * Use an ExactCard with empty PrintingInfo if you want to remove a card by name regardless of printing. + * Otherwise, it won't remove anything unless there's an exact printing match. + * + * @param card Card to decrement. + * @param zoneName Zone to decrement from. + */ + void decrementCard(const ExactCard &card, const QString &zoneName); /** @brief Opens a recently opened deck file. */ void actOpenRecent(const QString &fileName); @@ -166,8 +180,15 @@ public slots: /** @brief Requests closing the tab. */ bool closeRequest() override; - /** @brief Shows the printing selector dock. Pure virtual. */ - virtual void showPrintingSelector() = 0; + /** @brief Shows the printing selector dock and updates it with the current card. */ + void showPrintingSelector(); + + /** + * @brief Opens an EDHRec tab for the given card + * @param info The card + * @param isCommander The type of search + */ + void openEdhrecTab(const CardInfoPtr &info, bool isCommander); signals: /** @brief Emitted when a deck should be opened in a new editor tab. */ @@ -293,9 +314,6 @@ protected: */ QMessageBox *createSaveConfirmationWindow(); - /** @brief Helper function to add a card to a specific deck zone. */ - void addCardHelper(const ExactCard &card, const QString &zoneName); - /** @brief Opens a deck from a file. */ virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h index ac1b66e14..5b5c16302 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h @@ -98,55 +98,79 @@ inline static DeckFormat apiNameToFormat(const QString &name) { const QString n = name.trimmed(); - if (n.compare("Standard", Qt::CaseInsensitive) == 0) + if (n.compare("Standard", Qt::CaseInsensitive) == 0) { return DeckFormat::Standard; - if (n.compare("Modern", Qt::CaseInsensitive) == 0) + } + if (n.compare("Modern", Qt::CaseInsensitive) == 0) { return DeckFormat::Modern; - if (n.compare("Commander", Qt::CaseInsensitive) == 0) + } + if (n.compare("Commander", Qt::CaseInsensitive) == 0) { return DeckFormat::Commander; - if (n.compare("Legacy", Qt::CaseInsensitive) == 0) + } + if (n.compare("Legacy", Qt::CaseInsensitive) == 0) { return DeckFormat::Legacy; - if (n.compare("Vintage", Qt::CaseInsensitive) == 0) + } + if (n.compare("Vintage", Qt::CaseInsensitive) == 0) { return DeckFormat::Vintage; - if (n.compare("Pauper", Qt::CaseInsensitive) == 0) + } + if (n.compare("Pauper", Qt::CaseInsensitive) == 0) { return DeckFormat::Pauper; - if (n.compare("Custom", Qt::CaseInsensitive) == 0) + } + if (n.compare("Custom", Qt::CaseInsensitive) == 0) { return DeckFormat::Custom; - if (n.compare("Frontier", Qt::CaseInsensitive) == 0) + } + if (n.compare("Frontier", Qt::CaseInsensitive) == 0) { return DeckFormat::Frontier; - if (n.compare("Future Std", Qt::CaseInsensitive) == 0) + } + if (n.compare("Future Std", Qt::CaseInsensitive) == 0) { return DeckFormat::FutureStandard; - if (n.compare("Penny Dreadful", Qt::CaseInsensitive) == 0) + } + if (n.compare("Penny Dreadful", Qt::CaseInsensitive) == 0) { return DeckFormat::PennyDreadful; - if (n.compare("1v1 Commander", Qt::CaseInsensitive) == 0) + } + if (n.compare("1v1 Commander", Qt::CaseInsensitive) == 0) { return DeckFormat::Commander1v1; - if (n.compare("Dual Commander", Qt::CaseInsensitive) == 0) + } + if (n.compare("Dual Commander", Qt::CaseInsensitive) == 0) { return DeckFormat::DualCommander; - if (n.compare("Brawl", Qt::CaseInsensitive) == 0) + } + if (n.compare("Brawl", Qt::CaseInsensitive) == 0) { return DeckFormat::Brawl; + } - if (n.compare("Alchemy", Qt::CaseInsensitive) == 0) + if (n.compare("Alchemy", Qt::CaseInsensitive) == 0) { return DeckFormat::Alchemy; - if (n.compare("Historic", Qt::CaseInsensitive) == 0) + } + if (n.compare("Historic", Qt::CaseInsensitive) == 0) { return DeckFormat::Historic; - if (n.compare("Gladiator", Qt::CaseInsensitive) == 0) + } + if (n.compare("Gladiator", Qt::CaseInsensitive) == 0) { return DeckFormat::Gladiator; - if (n.compare("Oathbreaker", Qt::CaseInsensitive) == 0) + } + if (n.compare("Oathbreaker", Qt::CaseInsensitive) == 0) { return DeckFormat::Oathbreaker; - if (n.compare("Old School", Qt::CaseInsensitive) == 0) + } + if (n.compare("Old School", Qt::CaseInsensitive) == 0) { return DeckFormat::OldSchool; - if (n.compare("Pauper Commander", Qt::CaseInsensitive) == 0) + } + if (n.compare("Pauper Commander", Qt::CaseInsensitive) == 0) { return DeckFormat::PauperCommander; - if (n.compare("Pioneer", Qt::CaseInsensitive) == 0) + } + if (n.compare("Pioneer", Qt::CaseInsensitive) == 0) { return DeckFormat::Pioneer; - if (n.compare("PreDH", Qt::CaseInsensitive) == 0) + } + if (n.compare("PreDH", Qt::CaseInsensitive) == 0) { return DeckFormat::PreDH; - if (n.compare("Premodern", Qt::CaseInsensitive) == 0) + } + if (n.compare("Premodern", Qt::CaseInsensitive) == 0) { return DeckFormat::Premodern; - if (n.compare("Standard Brawl", Qt::CaseInsensitive) == 0) + } + if (n.compare("Standard Brawl", Qt::CaseInsensitive) == 0) { return DeckFormat::StandardBrawl; - if (n.compare("Timeless", Qt::CaseInsensitive) == 0) + } + if (n.compare("Timeless", Qt::CaseInsensitive) == 0) { return DeckFormat::Timeless; + } return DeckFormat::Unknown; } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp index 5b879f8ae..46c229156 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -21,16 +21,16 @@ void ArchidektApiResponseCard::fromJson(const QJsonObject &json) edition.fromJson(json.value("edition").toObject()); flavor = json.value("flavor").toString(); - //! \todo but not really important - //! \todo games = {""}; - //! \todo options = {""}; + //! \todo Parse games and options fields (not really important). + // games = {""}; + // options = {""}; scryfallImageHash = json.value("scryfallImageHash").toString(); oracleCard = json.value("oracleCard").toObject(); owned = json.value("owned").toInt(); pinnedStatus = json.value("pinnedStatus").toInt(); rarity = json.value("rarity").toString(); - //! \todo but not really important - //! \todo globalCategories = {""}; + //! \todo Parse globalCategories field (not really important). + // globalCategories = {""}; } void ArchidektApiResponseCard::debugPrint() const diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h index 265498228..559580da2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h @@ -24,7 +24,7 @@ public: QJsonObject getOracleCard() const { return oracleCard; - }; + } QString getCollectorNumber() const { diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp index 7a424de8b..f51d0f3e7 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp @@ -4,10 +4,29 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) { id = json.value("id").toInt(); + categories.clear(); + auto categoriesJson = json.value("categories").toArray(); - for (auto category : categoriesJson) { - categories.append(category.toString()); + for (const auto &categoryValue : categoriesJson) { + Category cat; + + if (categoryValue.isObject()) { + QJsonObject obj = categoryValue.toObject(); + + cat.id = obj.value("id").toInt(); + cat.name = obj.value("name").toString(); + cat.isPremier = obj.value("isPremier").toBool(); + cat.includedInDeck = obj.value("includedInDeck").toBool(); + cat.includedInPrice = obj.value("includedInPrice").toBool(); + } else if (categoryValue.isString()) { + cat.name = categoryValue.toString(); + + // assume mainboard unless known otherwise + cat.includedInDeck = true; + } + + categories.append(cat); } companion = json.value("companion").toBool(); @@ -27,7 +46,13 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) void ArchidektApiResponseCardEntry::debugPrint() const { qDebug() << "Id:" << id; - qDebug() << "Categories:" << categories; + for (auto category : categories) { + qDebug() << "Category ID:" << category.id; + qDebug() << "Category Name:" << category.name; + qDebug() << "Category Premier:" << category.isPremier; + qDebug() << "Category Included in Deck:" << category.includedInDeck; + qDebug() << "Category Included in Price:" << category.includedInPrice; + } qDebug() << "Companion:" << companion; qDebug() << "FlippedDefault:" << flippedDefault; qDebug() << "Label:" << label; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h index f7f86e9ed..118cf5b13 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h @@ -9,6 +9,15 @@ #include #include +struct Category +{ + int id; + QString name; + bool isPremier; + bool includedInDeck; + bool includedInPrice; +}; + class ArchidektApiResponseCardEntry { public: @@ -24,9 +33,9 @@ public: ArchidektApiResponseCard getCard() const { return card; - }; + } - QStringList getCategories() const + QList getCategories() const { return categories; } @@ -38,7 +47,7 @@ public: private: int id; - QStringList categories; + QList categories; bool companion; bool flippedDefault; QString label; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp index 98d8c6c8c..a27d8d961 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp @@ -44,7 +44,7 @@ void ArchidektApiResponseDeck::fromJson(const QJsonObject &json) cards.append(entry); } - // TODO but not really important + //! \todo Parse customCards field (not really important). // customCards = {""}; } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h index fce437751..b539d9dd1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -27,7 +27,7 @@ public: QVector getCards() const { return cards; - }; + } QVector getCategories() const { @@ -37,7 +37,7 @@ public: QString getDeckName() const { return name; - }; + } int getDeckFormat() const { diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp index 16ea60487..416621b41 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp @@ -23,7 +23,7 @@ void ArchidektApiResponseDeckListingContainer::fromJson(const QJsonObject &json) theoryCrafted = json.value("theoryCrafted").toBool(); game = json.value("game").toString(); hasDescription = json.value("hasDescription").toBool(); - // TODO + //! \todo Parse tags field. // tags = {""}; parentFolderId = json.value("parentFolderId").toInt(); owner.fromJson(json.value("owner").toObject()); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp index 2ba926f8b..3d6eac03d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp @@ -7,7 +7,7 @@ void ArchidektApiResponseDeckOwner::fromJson(const QJsonObject &json) avatar = QUrl(json.value("avatar").toString()); moderator = json.value("moderator").toBool(); pledgeLevel = json.value("pledgeLevel").toInt(); - // TODO but not really important + //! \todo Parse roles field (not really important). // roles = {""}; } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 8b17cd49e..66b68d823 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -63,16 +63,60 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi QString tempDeck; QTextStream deckStream(&tempDeck); - for (auto card : response.getCards()) { + QString mainboardText; + QString sideboardText; + + QTextStream mainStream(&mainboardText); + QTextStream sideStream(&sideboardText); + + for (const auto &card : response.getCards()) { QString fullName = card.getCard().getOracleCard().value("name").toString(); // We don't really care about the second card, the card database already has it as a relation QString cleanName = fullName.split("//").first().trimmed(); - tempDeck += QString("%1 %2 (%3) %4\n") - .arg(card.getQuantity()) - .arg(cleanName) - .arg(card.getCard().getEdition().getEditionCode().toUpper()) - .arg(card.getCard().getCollectorNumber()); + QString line = QString("%1 %2 (%3) %4\n") + .arg(card.getQuantity()) + .arg(cleanName) + .arg(card.getCard().getEdition().getEditionCode().toUpper()) + .arg(card.getCard().getCollectorNumber()); + + bool isCommander = false; + bool isSideboardCategory = false; + bool includedInDeck = false; + + for (const auto &cat : card.getCategories()) { + + if (cat.name.compare("Commander", Qt::CaseInsensitive) == 0) { + isCommander = true; + } + + if (cat.name.compare("Sideboard", Qt::CaseInsensitive) == 0 || + cat.name.compare("Maybeboard", Qt::CaseInsensitive) == 0) { + isSideboardCategory = true; + } + + if (cat.includedInDeck) { + includedInDeck = true; + } + } + + QString target; + + if (isCommander || isSideboardCategory) { + sideStream << line; + } else if (includedInDeck) { + mainStream << line; + } else { + sideStream << line; + } + } + + // Combine with blank line separator + tempDeck = mainboardText; + + if (!sideboardText.isEmpty()) { + tempDeck += "\n"; + tempDeck += sideboardText; } model = new DeckListModel(this); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index c26220869..70156df79 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -20,21 +20,27 @@ static QString timeAgo(const QString ×tamp) { QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); - if (!dt.isValid()) + if (!dt.isValid()) { return timestamp; // fallback if parsing fails + } qint64 secs = dt.secsTo(QDateTime::currentDateTimeUtc()); - if (secs < 60) + if (secs < 60) { return QString("%1 seconds ago").arg(secs); - if (secs < 3600) + } + if (secs < 3600) { return QString("%1 minutes ago").arg(secs / 60); - if (secs < 86400) + } + if (secs < 86400) { return QString("%1 hours ago").arg(secs / 3600); - if (secs < 30 * 86400) + } + if (secs < 30 * 86400) { return QString("%1 days ago").arg(secs / 86400); - if (secs < 365 * 86400) + } + if (secs < 365 * 86400) { return QString("%1 months ago").arg(secs / (30 * 86400)); + } return QString("%1 years ago").arg(secs / (365 * 86400)); } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h index 479de5fc8..f9a887aa4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -82,16 +82,16 @@ public: void resizeEvent(QResizeEvent *event) override; private: - /// Slider controlling the scale of card thumbnails in all deck entry widgets. + /** @brief Slider controlling the scale of card thumbnails in all deck entry widgets. */ CardSizeWidget *cardSizeSlider; - /// Main horizontal layout containing the FlowWidget. + /** @brief Main horizontal layout containing the FlowWidget. */ QHBoxLayout *layout; - /// Container providing scrollable multi-row flow layout of deck entries. + /** @brief Container providing scrollable multi-row flow layout of deck entries. */ FlowWidget *flowWidget; - /// Shared network manager used to download card images for all child entry widgets. + /** @brief Shared network manager used to download card images for all child entry widgets. */ QNetworkAccessManager *imageNetworkManager; }; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp index 352d55c79..9cac74d50 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -93,10 +93,11 @@ void TabArchidekt::initializeUi() colorLayout->addWidget(manaSymbol); connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) { - if (active) + if (active) { activeColors.insert(c); - else + } else { activeColors.remove(c); + } doSearch(); }); } @@ -298,16 +299,18 @@ void TabArchidekt::setupFilterWidgets() searchModel->updateSearchResults(text); QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); - if (!text.isEmpty()) + if (!text.isEmpty()) { completer->complete(); + } }); connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) { searchModel->updateSearchResults(text); QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); - if (!text.isEmpty()) + if (!text.isEmpty()) { completer->complete(); + } }); // Assemble secondary toolbar @@ -492,12 +495,13 @@ QString TabArchidekt::buildSearchUrl() QString logic = "GTE"; QString selected = minDeckSizeLogicCombo->currentText(); - if (selected == "≥") + if (selected == "≥") { logic = "GTE"; - else if (selected == "≤") + } else if (selected == "≤") { logic = "LTE"; - else + } else { logic = ""; + } if (!logic.isEmpty()) { query.addQueryItem("sizeLogic", logic); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h index cd4f9d99c..7bc97c431 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h @@ -1,8 +1,8 @@ /** * @file edhrec_deck_api_response.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_DECK_API_RESPONSE_H #define EDHREC_DECK_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h index 66a407acd..bd874d067 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_prices.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H #define EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h index 07b53dd97..d76dda490 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_container.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CONTAINER_ENTRY_H #define CONTAINER_ENTRY_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h index 86d78c5e3..931850ea9 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_details.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_VIEW_H #define CARD_VIEW_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h index 15691fbe4..2df29de0c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_list.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_LIST_H #define CARD_LIST_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h index 629eb8273..293e9a150 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h @@ -1,8 +1,8 @@ /** * @file edhrec_commander_api_response_commander_details.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H #define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h index fb89c0b12..695498e2b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_cards_api_response.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_CARDS_API_RESPONSE_H #define EDHREC_TOP_CARDS_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h index 26c5afedb..10aa51ff4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_commanders_api_response.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_H #define EDHREC_TOP_COMMANDERS_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h index 48219893a..0337d94f8 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_tags_api_response.h * @ingroup ApiResponses - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_TAGS_API_RESPONSE_H #define EDHREC_TOP_TAGS_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h index 80d32a394..8943ae9f3 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_prices_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H #define EDHREC_API_RESPONSE_CARD_PRICES_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index c01c7fa43..9d45f254b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -3,6 +3,7 @@ #include "../../../../../general/display/background_plate_widget.h" #include "../../tab_edhrec_main.h" +#include #include EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget( @@ -48,7 +49,7 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi if (parentTab) { cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value()); connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this, - &EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation); + &EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent); connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, cardPictureWidget, &CardInfoPictureWidget::setScaleFactor); connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab, @@ -59,7 +60,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi void EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); - actRequestPageNavigation(); + if (event->button() == Qt::LeftButton) { + actRequestPageNavigation(); + } } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index 82dcfb8b0..5daf32412 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_details_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h index 43baddb4c..516d820eb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_inclusion_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H #define EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h index d34c3da34..555da6ecf 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_list_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h index c2e1c018c..5800b5497 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_card_synergy_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H #define EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 8e74588e2..4201ce3b3 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_api_response_commander_details_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h index 1678e1380..80da8a7f8 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_commander_api_response_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index 10dfa8223..9b46b54ba 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_commander_api_response_navigation_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H #define EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h index e2748bf10..30998391c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_cards_api_response_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H #define EDHREC_TOP_CARDS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h index 766518c02..2b9563ea5 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_commanders_api_response_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H #define EDHREC_TOP_COMMANDERS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h index 64228b85d..4a4fc8991 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h @@ -1,8 +1,8 @@ /** * @file edhrec_top_tags_api_response_display_widget.h * @ingroup ApiResponseDisplayWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H #define EDHREC_TOP_TAGS_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.h index b5ee8d2ca..5ce36c9cf 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.h @@ -1,8 +1,8 @@ /** * @file tab_edhrec.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_EDHREC_H #define TAB_EDHREC_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h index 04cf03fc1..a7ed83efd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h @@ -1,8 +1,8 @@ /** * @file tab_edhrec_main.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_EDHREC_MAIN_H #define TAB_EDHREC_MAIN_H diff --git a/cockatrice/src/interface/widgets/tabs/tab.h b/cockatrice/src/interface/widgets/tabs/tab.h index 7cff01130..6ea1f5077 100644 --- a/cockatrice/src/interface/widgets/tabs/tab.h +++ b/cockatrice/src/interface/widgets/tabs/tab.h @@ -1,8 +1,8 @@ /** * @file tab.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_H #define TAB_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_account.h b/cockatrice/src/interface/widgets/tabs/tab_account.h index e09c12eb2..887038ebb 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_account.h +++ b/cockatrice/src/interface/widgets/tabs/tab_account.h @@ -1,8 +1,8 @@ /** * @file tab_account.h * @ingroup AccountTabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_ACCOUNT_H #define TAB_ACCOUNT_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_admin.h b/cockatrice/src/interface/widgets/tabs/tab_admin.h index e1935e351..0da098ba5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_admin.h +++ b/cockatrice/src/interface/widgets/tabs/tab_admin.h @@ -1,8 +1,8 @@ /** * @file tab_admin.h * @ingroup ServerTabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_ADMIN_H #define TAB_ADMIN_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 59ee6bdeb..4e7cbfecf 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -81,8 +81,9 @@ void TabDeckEditor::createMenus() QString TabDeckEditor::getTabText() const { QString result = tr("Deck: %1").arg(deckStateManager->getSimpleDeckName()); - if (deckStateManager->isModified()) + if (deckStateManager->isModified()) { result.prepend("* "); + } return result; } @@ -119,16 +120,6 @@ void TabDeckEditor::refreshShortcuts() aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout")); } -/** - * @brief Displays the printing selector dock with the current card. - */ -void TabDeckEditor::showPrintingSelector() -{ - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); - printingSelectorDockWidget->printingSelector->updateDisplay(); - printingSelectorDockWidget->setVisible(true); -} - /** * @brief Loads deck editor layout from settings or resets to default. */ @@ -137,9 +128,9 @@ void TabDeckEditor::loadLayout() LayoutsSettings &layouts = SettingsCache::instance().layouts(); auto layoutState = layouts.getDeckEditorLayoutState(); - if (layoutState.isNull()) + if (layoutState.isNull()) { restartLayout(); - else { + } else { restoreState(layoutState); restoreGeometry(layouts.getDeckEditorGeometry()); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h index ab7a0bfc5..14be59cd7 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h @@ -83,10 +83,6 @@ public: /** @brief Creates menus for deck editing and view options. */ void createMenus() override; - -public slots: - /** @brief Shows the printing selector dock and updates it with current card. */ - void showPrintingSelector() override; }; #endif diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 26e3f2ecf..bdf7901f1 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -181,8 +181,9 @@ void TabDeckStorage::retranslateUi() QString TabDeckStorage::getTargetPath() const { RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); - if (curRight == nullptr) + if (curRight == nullptr) { return {}; + } auto *dir = dynamic_cast(curRight); if (dir == nullptr) { dir = dynamic_cast(curRight->getParent()); @@ -237,13 +238,15 @@ void TabDeckStorage::actOpenLocalDeck() { QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); for (const auto &curLeft : curLefts) { - if (localDirModel->isDir(curLeft)) + if (localDirModel->isDir(curLeft)) { continue; + } QString filePath = localDirModel->filePath(curLeft); std::optional deckOpt = DeckLoader::loadFromFile(filePath, DeckFileFormat::Cockatrice, true); - if (!deckOpt) + if (!deckOpt) { continue; + } emit openDeckEditor(deckOpt.value()); } @@ -320,10 +323,12 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa QString deckName = getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"), QLineEdit::Normal, deckFileInfo.completeBaseName(), &ok); - if (!ok) + if (!ok) { return; - if (deckName.isEmpty()) + } + if (deckName.isEmpty()) { deckName = tr("Unnamed deck"); + } deck.setName(deckName); } else { deck.setName(deck.getName().left(MAX_NAME_LENGTH)); @@ -372,8 +377,9 @@ void TabDeckStorage::actNewLocalFolder() bool ok; QString folderName = QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok); - if (!ok || folderName.isEmpty()) + if (!ok || folderName.isEmpty()) { return; + } localDirModel->mkdir(dirIndex, folderName); } @@ -387,8 +393,9 @@ void TabDeckStorage::actDeleteLocalDeck() } if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; + } for (const auto &curLeft : curLefts) { if (curLeft.isValid()) { @@ -414,8 +421,9 @@ void TabDeckStorage::actOpenRemoteDeck() { for (const auto &curRight : serverDirView->getCurrentSelection()) { RemoteDeckList_TreeModel::FileNode *node = dynamic_cast(curRight); - if (!node) + if (!node) { continue; + } Command_DeckDownload cmd; cmd.set_deck_id(node->getId()); @@ -428,15 +436,17 @@ void TabDeckStorage::actOpenRemoteDeck() void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext); std::optional deckOpt = DeckLoader::loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()); - if (!deckOpt) + if (!deckOpt) { return; + } emit openDeckEditor(deckOpt.value()); } @@ -488,8 +498,9 @@ void TabDeckStorage::downloadFinished(const Response &r, const CommandContainer & /*commandContainer*/, const QVariant &extraData) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); QString filePath = extraData.toString(); @@ -504,12 +515,14 @@ void TabDeckStorage::actNewFolder() QString targetPath = getTargetPath(); int max_length = MAX_NAME_LENGTH - targetPath.length() - 1; // generated length would be path + / + name - if (max_length < 1) // can't create path that's short enough + if (max_length < 1) { // can't create path that's short enough return; + } QString folderName = getTextWithMax(this, tr("New folder"), tr("Name of new folder:"), max_length); - if (folderName.isEmpty()) + if (folderName.isEmpty()) { return; + } // '/' isn't a valid filename character on *nix so we're choosing to replace it with a different arbitrary // character. @@ -527,8 +540,9 @@ void TabDeckStorage::actNewFolder() void TabDeckStorage::newFolderFinished(const Response &response, const CommandContainer &commandContainer) { - if (response.response_code() != Response::RespOk) + if (response.response_code() != Response::RespOk) { return; + } const Command_DeckNewDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckNewDir::ext); serverDirView->addFolderToTree(QString::fromStdString(cmd.dir_name()), @@ -562,8 +576,9 @@ void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curR PendingCommand *pend; if (const auto *dir = dynamic_cast(curRight)) { QString targetPath = dir->getPath(); - if (targetPath.isEmpty()) + if (targetPath.isEmpty()) { return; + } if (targetPath.length() > MAX_NAME_LENGTH) { qCritical() << "target path to delete is too long" << targetPath; return; @@ -585,22 +600,26 @@ void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curR void TabDeckStorage::deleteDeckFinished(const Response &response, const CommandContainer &commandContainer) { - if (response.response_code() != Response::RespOk) + if (response.response_code() != Response::RespOk) { return; + } const Command_DeckDel &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDel::ext); RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeById(cmd.deck_id()); - if (toDelete) + if (toDelete) { serverDirView->removeNode(toDelete); + } } void TabDeckStorage::deleteFolderFinished(const Response &response, const CommandContainer &commandContainer) { - if (response.response_code() != Response::RespOk) + if (response.response_code() != Response::RespOk) { return; + } const Command_DeckDelDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDelDir::ext); RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeByPath(QString::fromStdString(cmd.path())); - if (toDelete) + if (toDelete) { serverDirView->removeNode(toDelete); + } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h index f8c0497f7..a863e0625 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h @@ -2,8 +2,8 @@ * @file tab_deck_storage.h * @ingroup DeckStorageWidgets * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_DECK_STORAGE_H #define TAB_DECK_STORAGE_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index cf8269069..a81161e83 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -1,18 +1,21 @@ #include "tab_game.h" #include "../../../client/settings/cache_settings.h" -#include "../game/board/arrow_item.h" -#include "../game/board/card_item.h" -#include "../game/deckview/deck_view_container.h" -#include "../game/deckview/tabbed_deck_view_container.h" #include "../game/game.h" -#include "../game/game_scene.h" -#include "../game/game_view.h" -#include "../game/log/message_log_widget.h" -#include "../game/phases_toolbar.h" -#include "../game/player/player.h" -#include "../game/player/player_list_widget.h" +#include "../game/player/player_logic.h" #include "../game/replay.h" +#include "../game_graphics/board/arrow_item.h" +#include "../game_graphics/board/card_item.h" +#include "../game_graphics/deckview/deck_view_container.h" +#include "../game_graphics/deckview/tabbed_deck_view_container.h" +#include "../game_graphics/game_scene.h" +#include "../game_graphics/game_view.h" +#include "../game_graphics/log/message_log_widget.h" +#include "../game_graphics/phases_toolbar.h" +#include "../game_graphics/player/menu/card_menu.h" +#include "../game_graphics/player/menu/player_menu.h" +#include "../game_graphics/player/player_graphics_item.h" +#include "../game_graphics/player/player_list_widget.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/dialogs/dlg_create_game.h" @@ -47,7 +50,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr) { // THIS CTOR IS USED ON REPLAY - game = new Replay(this, _replay); + game = new Replay(this, _replay, tabSupervisor->getIsLocalGame()); createCardInfoDock(true); createPlayerListDock(true); @@ -91,7 +94,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()) { // THIS CTOR IS USED ON GAMES - game = new Game(this, _clients, event, _roomGameTypes); + game = new Game(this, tabSupervisor->getIsLocalGame(), _clients, event, _roomGameTypes); createCardInfoDock(); createPlayerListDock(); @@ -125,8 +128,9 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, refreshShortcuts(); // append game to rooms game list for others to see - for (int i = game->getGameMetaInfo()->gameTypesSize() - 1; i >= 0; i--) + for (int i = game->getGameMetaInfo()->gameTypesSize() - 1; i >= 0; i--) { gameTypes.append(game->getGameMetaInfo()->findRoomGameType(i)); + } QTimer::singleShot(0, this, &TabGame::loadLayout); } @@ -259,6 +263,9 @@ TabGame::~TabGame() if (replayManager) { delete replayManager->replay; } + for (auto &player : game->getPlayerManager()->getPlayers()) { + player->clear(); + } } void TabGame::updatePlayerListDockTitle() @@ -279,12 +286,14 @@ void TabGame::retranslateUi() updatePlayerListDockTitle(); cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString())); - if (replayDock) + if (replayDock) { replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString())); + } if (phasesMenu) { - for (int i = 0; i < phaseActions.size(); ++i) + for (int i = 0; i < phaseActions.size(); ++i) { phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); + } phasesMenu->setTitle(tr("&Phases")); } @@ -310,8 +319,9 @@ void TabGame::retranslateUi() if (aRotateViewCCW) { aRotateViewCCW->setText(tr("Rotate View Co&unterclockwise")); } - if (aGameInfo) + if (aGameInfo) { aGameInfo->setText(tr("Game &information")); + } if (aConcede) { if (game->getPlayerManager()->isMainPlayerConceded()) { aConcede->setText(tr("Un&concede")); @@ -356,13 +366,14 @@ void TabGame::retranslateUi() cardInfoFrameWidget->retranslateUi(); - QMapIterator i(game->getPlayerManager()->getPlayers()); + for (auto playerView : scene->getPlayers().values()) { + playerView->retranslateUi(); + } - while (i.hasNext()) - i.next().value()->getGraphicsItem()->retranslateUi(); QMapIterator j(deckViewContainers); - while (j.hasNext()) + while (j.hasNext()) { j.next().value()->playerDeckView->retranslateUi(); + } scene->retranslateUi(); } @@ -480,19 +491,22 @@ void TabGame::actGameInfo() void TabGame::actConcede() { - Player *player = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); - if (player == nullptr) + PlayerLogic *player = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + if (player == nullptr) { return; + } if (!player->getConceded()) { if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; + } emit game->getPlayerManager()->activeLocalPlayerConceded(); } else { if (QMessageBox::question(this, tr("Unconcede"), tr("You have already conceded. Do you want to return to this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; + } emit game->getPlayerManager()->activeLocalPlayerUnconceded(); } } @@ -508,20 +522,23 @@ bool TabGame::leaveGame() if (!game->getPlayerManager()->isSpectator()) { tabSupervisor->setCurrentWidget(this); if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return false; + } } - if (!replayDock) + if (!replayDock) { emit gameLeft(); + } } return true; } void TabGame::actSay() { - if (completer->popup()->isVisible()) + if (completer->popup()->isVisible()) { return; + } if (sayEdit->text().startsWith("/card ")) { cardInfoFrameWidget->setCard(sayEdit->text().mid(6)); @@ -566,8 +583,9 @@ void TabGame::actPhaseAction() void TabGame::actNextPhase() { int phase = game->getGameState()->getCurrentPhase(); - if (++phase >= phasesToolbar->phaseCount()) + if (++phase >= phasesToolbar->phaseCount()) { phase = 0; + } emit phaseChanged(phase); } @@ -590,16 +608,9 @@ void TabGame::actNextPhaseAction() void TabGame::actRemoveLocalArrows() { - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) { - Player *player = playerIterator.next().value(); - if (!player->getPlayerInfo()->getLocal()) - continue; - QMapIterator arrowIterator(player->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *a = arrowIterator.next().value(); - emit arrowDeletionRequested(a->getId()); - } + auto *local = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + if (local) { + scene->clearArrowsForPlayer(local->getPlayerInfo()->getId()); } } @@ -638,15 +649,19 @@ void TabGame::notifyPlayerKicked() msgBox.exec(); } -Player *TabGame::addPlayer(Player *newPlayer) +PlayerLogic *TabGame::addPlayer(PlayerLogic *newPlayer) { QString newPlayerName = "@" + newPlayer->getPlayerInfo()->getName(); addPlayerToAutoCompleteList(newPlayerName); scene->addPlayer(newPlayer); - connect(newPlayer, &Player::newCardAdded, this, &TabGame::newCardAdded); - connect(newPlayer->getPlayerMenu(), &PlayerMenu::cardMenuUpdated, this, &TabGame::setCardMenu); + auto *view = scene->viewForPlayer(newPlayer->getPlayerInfo()->getId()); + + connect(newPlayer, &PlayerLogic::newCardAdded, this, &TabGame::newCardAdded); + connect(newPlayer, &PlayerLogic::openDeckEditor, this, &TabGame::openDeckEditor); + connect(view->getPlayerMenu(), &PlayerMenu::cardMenuUpdated, this, &TabGame::setCardMenu); + connect(view, &PlayerGraphicsItem::cardInfoRequested, this, &TabGame::viewCardInfo); messageLog->connectToPlayerEventHandler(newPlayer->getPlayerEventHandler()); @@ -659,17 +674,17 @@ Player *TabGame::addPlayer(Player *newPlayer) addLocalPlayer(newPlayer, newPlayer->getPlayerInfo()->getId()); } - gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()->getPlayerMenu()); + gameMenu->insertMenu(playersSeparator, view->getPlayerMenu()->getPlayerMenu()); createZoneForPlayer(newPlayer, newPlayer->getPlayerInfo()->getId()); return newPlayer; } -void TabGame::addLocalPlayer(Player *newPlayer, int playerId) +void TabGame::addLocalPlayer(PlayerLogic *newPlayer, int playerId) { if (game->getGameState()->getClients().size() == 1) { - newPlayer->getPlayerMenu()->setShortcutsActive(); + scene->viewForPlayer(playerId)->getPlayerMenu()->setShortcutsActive(); } auto *deckView = new TabbedDeckViewContainer(playerId, this); @@ -687,29 +702,26 @@ void TabGame::addLocalPlayer(Player *newPlayer, int playerId) } } -void TabGame::processPlayerLeave(Player *leavingPlayer) +void TabGame::processPlayerLeave(PlayerLogic *leavingPlayer) { - QString playerName = "@" + leavingPlayer->getPlayerInfo()->getName(); - removePlayerFromAutoCompleteList(playerName); - - scene->removePlayer(leavingPlayer); + removePlayerFromAutoCompleteList("@" + leavingPlayer->getPlayerInfo()->getName()); // When we inserted the playerMenu into the gameMenu earlier, Qt wrapped the playerMenu into a QAction*, which lives // independently and does not get cleaned up when the source menu gets destroyed. We have to manually clean here. - if (leavingPlayer->getPlayerMenu()) { - QMenu *menu = leavingPlayer->getPlayerMenu()->getPlayerMenu(); - if (menu) { - // Find and remove the QAction pointing to this menu - QList actions = gameMenu->actions(); - for (QAction *act : actions) { - if (act->menu() == menu) { - gameMenu->removeAction(act); - delete act; // deletes the QAction wrapper around the submenu - break; - } + auto *view = scene->viewForPlayer(leavingPlayer->getPlayerInfo()->getId()); + if (view) { + // Find and remove the QAction pointing to this menu + QMenu *menu = view->getPlayerMenu()->getPlayerMenu(); + for (QAction *act : gameMenu->actions()) { + if (act->menu() == menu) { + gameMenu->removeAction(act); + delete act; + break; } } } + + scene->removePlayer(leavingPlayer); } void TabGame::processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName) @@ -734,13 +746,13 @@ void TabGame::processMultipleRemotePlayerDeckSelect(QVectorplayerDeckView->setReadyStart(ready); } -void TabGame::createZoneForPlayer(Player *newPlayer, int playerId) +void TabGame::createZoneForPlayer(PlayerLogic *newPlayer, int playerId) { if (!game->getPlayerManager()->getSpectators().contains(playerId)) { @@ -803,9 +815,10 @@ void TabGame::startGame(bool _resuming) mainWidget->setCurrentWidget(gamePlayAreaWidget); if (!_resuming) { - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) + QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); + while (playerIterator.hasNext()) { playerIterator.next().value()->setGameStarted(); + } } playerListWidget->setGameStarted(true, game->getGameState()->isResuming()); @@ -845,25 +858,26 @@ void TabGame::closeGame() gameMenu->addAction(aLeaveGame); } -Player *TabGame::setActivePlayer(int id) +PlayerLogic *TabGame::setActivePlayer(int id) { - Player *player = game->getPlayerManager()->getPlayer(id); - if (!player) + PlayerLogic *player = game->getPlayerManager()->getPlayer(id); + if (!player) { return nullptr; + } playerListWidget->setActivePlayer(id); - QMapIterator i(game->getPlayerManager()->getPlayers()); + QMapIterator i(game->getPlayerManager()->getPlayers()); while (i.hasNext()) { i.next(); if (i.value() == player) { i.value()->setActive(true); if (game->getGameState()->getClients().size() > 1) { - i.value()->getPlayerMenu()->setShortcutsActive(); + scene->viewForPlayer(i.value()->getPlayerInfo()->getId())->getPlayerMenu()->setShortcutsActive(); } } else { i.value()->setActive(false); if (game->getGameState()->getClients().size() > 1) { - i.value()->getPlayerMenu()->setShortcutsInactive(); + scene->viewForPlayer(i.value()->getPlayerInfo()->getId())->getPlayerMenu()->setShortcutsInactive(); } } } @@ -879,8 +893,13 @@ void TabGame::setActivePhase(int phase) void TabGame::newCardAdded(AbstractCardItem *card) { + connect(card, &AbstractCardItem::rightClicked, scene, &GameScene::onCardRightClicked); + connect(card, &AbstractCardItem::playSelected, scene, &GameScene::playSelected); + connect(card, &AbstractCardItem::playSelectedFaceDown, scene, &GameScene::playSelectedFaceDown); + connect(card, &AbstractCardItem::hideSelected, scene, &GameScene::hideSelected); connect(card, &AbstractCardItem::hovered, cardInfoFrameWidget, qOverload(&CardInfoFrameWidget::setCard)); + connect(card, &AbstractCardItem::selectionChanged, scene, &GameScene::onCardSelectionChanged); connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(card, &AbstractCardItem::cardShiftClicked, this, &TabGame::linkCardToChat); @@ -891,26 +910,31 @@ QString TabGame::getTabText() const QString gameTypeInfo; if (!gameTypes.empty()) { gameTypeInfo = gameTypes.at(0); - if (gameTypes.size() > 1) + if (gameTypes.size() > 1) { gameTypeInfo.append("..."); + } } QString gameDesc(game->getGameMetaInfo()->description()); QString gameId(QString::number(game->getGameMetaInfo()->gameId())); QString tabText; - if (replayDock) + if (replayDock) { tabText.append(tr("Replay") + " "); - if (!gameTypeInfo.isEmpty()) - tabText.append(gameTypeInfo + " "); - if (!gameDesc.isEmpty()) { - if (gameDesc.length() >= 15) - tabText.append("| " + gameDesc.left(15) + "... "); - else - tabText.append("| " + gameDesc + " "); } - if (!tabText.isEmpty()) + if (!gameTypeInfo.isEmpty()) { + tabText.append(gameTypeInfo + " "); + } + if (!gameDesc.isEmpty()) { + if (gameDesc.length() >= 15) { + tabText.append("| " + gameDesc.left(15) + "... "); + } else { + tabText.append("| " + gameDesc + " "); + } + } + if (!tabText.isEmpty()) { tabText.append("| "); + } tabText.append("#" + gameId); return tabText; @@ -919,7 +943,7 @@ QString TabGame::getTabText() const /** * @param menu The menu to set. Pass in nullptr to set the menu to empty. */ -void TabGame::setCardMenu(QMenu *menu) +void TabGame::setCardMenu(CardMenu *menu) { if (!aCardMenu) { return; @@ -946,8 +970,6 @@ void TabGame::createMenuItems() connect(aReverseTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleReverseTurn); aRemoveLocalArrows = new QAction(this); connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows); - connect(this, &TabGame::arrowDeletionRequested, game->getGameEventHandler(), - &GameEventHandler::handleArrowDeletion); aRotateViewCW = new QAction(this); connect(aRotateViewCW, &QAction::triggered, this, &TabGame::actRotateViewCW); aRotateViewCCW = new QAction(this); @@ -1124,12 +1146,16 @@ void TabGame::actResetLayout() void TabGame::createPlayAreaWidget(bool bReplay) { phasesToolbar = new PhasesToolbar; - if (!bReplay) + if (!bReplay) { connect(phasesToolbar, &PhasesToolbar::sendGameCommand, game->getGameEventHandler(), qOverload(&GameEventHandler::sendGameCommand)); + } scene = new GameScene(phasesToolbar, this); connect(game->getPlayerManager(), &PlayerManager::playerConceded, scene, &GameScene::rearrange); connect(game->getPlayerManager(), &PlayerManager::playerCountChanged, scene, &GameScene::rearrange); + connect(scene, &GameScene::arrowDeletionRequested, game->getGameEventHandler(), + &GameEventHandler::handleArrowDeletion); + connect(game->getGameEventHandler(), &GameEventHandler::arrowDeleted, scene, &GameScene::deleteArrow); gameView = new GameView(scene); auto gamePlayAreaVBox = new QVBoxLayout; @@ -1151,6 +1177,11 @@ void TabGame::createReplayDock(GameReplay *replay) QDockWidget::DockWidgetMovable); replayDock->setWidget(replayManager); replayDock->setFloating(false); + + connect(replayManager, &ReplayManager::eventReplayed, game->getGameEventHandler(), + [this](const auto &event, auto options) { + game->getGameEventHandler()->processGameEventContainer(event, nullptr, options); + }); } void TabGame::createDeckViewContainerWidget(bool bReplay) diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index d8746ccc9..b9289432d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -3,15 +3,15 @@ * @ingroup Tabs * @ingroup GameWidgets * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_GAME_H #define TAB_GAME_H #include "../game/abstract_game.h" -#include "../game/log/message_log_widget.h" -#include "../game/player/player.h" +#include "../game/player/player_logic.h" +#include "../game_graphics/log/message_log_widget.h" #include "../interface/widgets/menus/tearoff_menu.h" #include "../interface/widgets/replay/replay_manager.h" #include "tab.h" @@ -20,6 +20,7 @@ #include #include +class CardMenu; class ServerInfo_PlayerProperties; class TabbedDeckViewContainer; inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game"); @@ -99,21 +100,21 @@ private: QMap dockToActions; - Player *addPlayer(Player *newPlayer); - void addLocalPlayer(Player *newPlayer, int playerId); + PlayerLogic *addPlayer(PlayerLogic *newPlayer); + void addLocalPlayer(PlayerLogic *newPlayer, int playerId); void processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName); void processMultipleRemotePlayerDeckSelect(QVector>> playerIdDeckMap); - void processLocalPlayerDeckSelect(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); - void loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); + void processLocalPlayerDeckSelect(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo); + void loadDeckForLocalPlayer(PlayerLogic *localPlayer, int playerId, ServerInfo_Player playerInfo); void processLocalPlayerReady(int playerId, ServerInfo_Player playerInfo); - void createZoneForPlayer(Player *newPlayer, int playerId); + void createZoneForPlayer(PlayerLogic *newPlayer, int playerId); void startGame(bool resuming); void stopGame(); void closeGame(); bool leaveGame(); - Player *setActivePlayer(int id); + PlayerLogic *setActivePlayer(int id); void setActivePhase(int phase); void createMenuItems(); void createReplayMenuItems(); @@ -137,12 +138,11 @@ signals: void gameLeft(); void chatMessageSent(QString chatMessage); void turnAdvanced(); - void arrowDeletionRequested(int arrowId); private slots: void adminLockChanged(bool lock); void newCardAdded(AbstractCardItem *card); - void setCardMenu(QMenu *menu); + void setCardMenu(CardMenu *menu); void actGameInfo(); void actConcede(); @@ -163,7 +163,7 @@ private slots: void actCompleterChanged(); void notifyPlayerJoin(QString playerName); void notifyPlayerKicked(); - void processPlayerLeave(Player *leavingPlayer); + void processPlayerLeave(PlayerLogic *leavingPlayer); void actResetLayout(); void hideEvent(QHideEvent *event) override; diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.h b/cockatrice/src/interface/widgets/tabs/tab_home.h index c40dfc269..3aae19c2b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.h +++ b/cockatrice/src/interface/widgets/tabs/tab_home.h @@ -1,8 +1,8 @@ /** * @file tab_home.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_HOME_H #define TAB_HOME_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp index e4f699160..9a030e7d9 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp @@ -83,18 +83,22 @@ void TabLog::getClicked() privateChat->setChecked(true); } - if (maximumResults->value() == 0) + if (maximumResults->value() == 0) { maximumResults->setValue(1000); + } int dateRange = 0; - if (lastHour->isChecked()) + if (lastHour->isChecked()) { dateRange = 1; + } - if (today->isChecked()) + if (today->isChecked()) { dateRange = 24; + } - if (pastDays->isChecked()) + if (pastDays->isChecked()) { dateRange = pastXDays->value() * 24; + } Command_ViewLogHistory cmd; cmd.set_user_name(findUsername->text().toStdString()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.h b/cockatrice/src/interface/widgets/tabs/tab_logs.h index a73af651b..5d164dc92 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.h +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.h @@ -1,8 +1,8 @@ /** * @file tab_logs.h * @ingroup ServerTabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_LOG_H #define TAB_LOG_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.cpp b/cockatrice/src/interface/widgets/tabs/tab_message.cpp index bd0457990..d77cb0391 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_message.cpp @@ -72,8 +72,9 @@ void TabMessage::retranslateUi() void TabMessage::tabActivated() { - if (!sayEdit->hasFocus()) + if (!sayEdit->hasFocus()) { sayEdit->setFocus(); + } } QString TabMessage::getUserName() const @@ -94,8 +95,9 @@ void TabMessage::closeEvent(QCloseEvent *event) void TabMessage::sendMessage() { - if (sayEdit->text().isEmpty() || !userOnline) + if (sayEdit->text().isEmpty() || !userOnline) { return; + } Command_Message cmd; cmd.set_user_name(otherUserInfo->name()); @@ -110,9 +112,10 @@ void TabMessage::sendMessage() void TabMessage::messageSent(const Response &response) { - if (response.response_code() == Response::RespInIgnoreList) + if (response.response_code() == Response::RespInIgnoreList) { chatView->appendMessage(tr( "This user is ignoring you, they cannot see your messages in main chat and you cannot join their games.")); + } } void TabMessage::processUserMessageEvent(const Event_UserMessage &event) @@ -120,12 +123,15 @@ void TabMessage::processUserMessageEvent(const Event_UserMessage &event) auto userInfo = event.sender_name() == otherUserInfo->name() ? otherUserInfo : ownUserInfo; chatView->appendMessage(QString::fromStdString(event.message()), {}, *userInfo, true); - if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this)) + if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this)) { soundEngine->playSound("private_message"); - if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event)) + } + if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event)) { showSystemPopup(event); - if (QString::fromStdString(event.sender_name()).toLower().simplified() == "servatrice") + } + if (QString::fromStdString(event.sender_name()).toLower().simplified() == "servatrice") { sayEdit->setDisabled(true); + } emit userEvent(); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.h b/cockatrice/src/interface/widgets/tabs/tab_message.h index 36a0b5f78..0472bb061 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.h +++ b/cockatrice/src/interface/widgets/tabs/tab_message.h @@ -1,8 +1,8 @@ /** * @file tab_message.h * @ingroup MessagingTabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_MESSAGE_H #define TAB_MESSAGE_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 570677ada..af52c038f 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -256,13 +256,15 @@ void TabReplays::actOpenLocalReplay() { QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); for (const auto &curLeft : curLefts) { - if (localDirModel->isDir(curLeft)) + if (localDirModel->isDir(curLeft)) { continue; + } QString filePath = localDirModel->filePath(curLeft); QFile f(filePath); - if (!f.open(QIODevice::ReadOnly)) + if (!f.open(QIODevice::ReadOnly)) { continue; + } QByteArray _data = f.readAll(); f.close(); @@ -319,8 +321,9 @@ void TabReplays::actNewLocalFolder() bool ok; QString folderName = QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok); - if (!ok || folderName.isEmpty()) + if (!ok || folderName.isEmpty()) { return; + } localDirModel->mkdir(dirIndex, folderName); } @@ -378,8 +381,9 @@ void TabReplays::actOpenRemoteReplay() void TabReplays::openRemoteReplayFinished(const Response &r) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); GameReplay *replay = new GameReplay; @@ -438,8 +442,9 @@ void TabReplays::downloadFinished(const Response &r, const CommandContainer & /* commandContainer */, const QVariant &extraData) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); QString filePath = extraData.toString(); @@ -475,8 +480,9 @@ void TabReplays::actKeepRemoteReplay() void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Command_ReplayModifyMatch &cmd = commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext); @@ -513,8 +519,9 @@ void TabReplays::actDeleteRemoteReplay() void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) { - if (r.response_code() != Response::RespOk) + if (r.response_code() != Response::RespOk) { return; + } const Command_ReplayDeleteMatch &cmd = commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext); diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.h b/cockatrice/src/interface/widgets/tabs/tab_replays.h index fa1131cd5..aa4ec039c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.h +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.h @@ -2,8 +2,8 @@ * @file tab_replays.h * @ingroup Replays * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_REPLAYS_H #define TAB_REPLAYS_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index 92e38662b..424742e9b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -41,9 +41,10 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager()) { const int gameTypeListSize = info.gametype_list_size(); - for (int i = 0; i < gameTypeListSize; ++i) + for (int i = 0; i < gameTypeListSize; ++i) { gameTypes.insert(info.gametype_list(i).game_type_id(), QString::fromStdString(info.gametype_list(i).description())); + } QMap tempMap; tempMap.insert(info.room_id(), gameTypes); @@ -117,8 +118,9 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, userList->sortItems(); const int gameListSize = info.game_list_size(); - for (int i = 0; i < gameListSize; ++i) + for (int i = 0; i < gameListSize; ++i) { gameSelector->processGameInfo(info.game_list(i)); + } completer = new QCompleter(autocompleteUserList, sayEdit); completer->setCaseSensitivity(Qt::CaseInsensitive); @@ -182,8 +184,9 @@ void TabRoom::closeEvent(QCloseEvent *event) void TabRoom::tabActivated() { - if (!sayEdit->hasFocus()) + if (!sayEdit->hasFocus()) { sayEdit->setFocus(); + } } QString TabRoom::sanitizeHtml(QString dirty) const @@ -211,8 +214,9 @@ void TabRoom::sendMessage() void TabRoom::sayFinished(const Response &response) { - if (response.response_code() == Response::RespChatFlood) + if (response.response_code() == Response::RespChatFlood) { chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds.")); + } } void TabRoom::actClearChat() @@ -258,8 +262,9 @@ void TabRoom::processRoomEvent(const RoomEvent &event) void TabRoom::processListGamesEvent(const Event_ListGames &event) { const int gameListSize = event.game_list_size(); - for (int i = 0; i < gameListSize; ++i) + for (int i = 0; i < gameListSize; ++i) { gameSelector->processGameInfo(event.game_list(i)); + } } void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event) @@ -284,26 +289,30 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event) QString senderName = QString::fromStdString(event.name()); QString message = QString::fromStdString(event.message()); - if (userListProxy->isUserIgnored(senderName)) + if (userListProxy->isUserIgnored(senderName)) { return; + } UserListTWI *twi = userList->getUsers().value(senderName); ServerInfo_User userInfo = {}; if (twi) { userInfo = twi->getUserInfo(); if (SettingsCache::instance().getIgnoreUnregisteredUsers() && - !UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) + !UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) { return; + } } - if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().getRoomHistory()) + if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().getRoomHistory()) { return; + } - if (event.message_type() == Event_RoomSay::ChatHistory) + if (event.message_type() == Event_RoomSay::ChatHistory) { message = "[" + QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) + "] " + message; + } chatView->appendMessage(message, event.message_type(), userInfo, true); emit userEvent(false); diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index 67d9afc86..eeb5a9e14 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -2,8 +2,8 @@ * @file tab_room.h * @ingroup RoomTabs * @ingroup Lobby - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_ROOM_H #define TAB_ROOM_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.cpp b/cockatrice/src/interface/widgets/tabs/tab_server.cpp index aa52b4b1a..2fce5c1fa 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_server.cpp @@ -69,27 +69,35 @@ void RoomSelector::processListRoomsEvent(const Event_ListRooms &event) for (int j = 0; j < roomList->topLevelItemCount(); ++j) { QTreeWidgetItem *twi = roomList->topLevelItem(j); if (twi->data(0, Qt::UserRole).toInt() == room.room_id()) { - if (room.has_name()) + if (room.has_name()) { twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name())); - if (room.has_description()) + } + if (room.has_description()) { twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description())); - if (room.has_permissionlevel()) + } + if (room.has_permissionlevel()) { twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room)); - if (room.has_player_count()) + } + if (room.has_player_count()) { twi->setData(3, Qt::DisplayRole, room.player_count()); - if (room.has_game_count()) + } + if (room.has_game_count()) { twi->setData(4, Qt::DisplayRole, room.game_count()); + } return; } } QTreeWidgetItem *twi = new QTreeWidgetItem; twi->setData(0, Qt::UserRole, room.room_id()); - if (room.has_name()) + if (room.has_name()) { twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name())); - if (room.has_description()) + } + if (room.has_description()) { twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description())); - if (room.has_permissionlevel()) + } + if (room.has_permissionlevel()) { twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room)); + } twi->setData(3, Qt::DisplayRole, room.player_count()); twi->setData(4, Qt::DisplayRole, room.game_count()); twi->setTextAlignment(2, Qt::AlignRight); @@ -97,9 +105,11 @@ void RoomSelector::processListRoomsEvent(const Event_ListRooms &event) twi->setTextAlignment(4, Qt::AlignRight); roomList->addTopLevelItem(twi); - if (room.has_auto_join()) - if (room.auto_join()) + if (room.has_auto_join()) { + if (room.auto_join()) { emit joinRoomRequest(room.room_id(), false); + } + } } } @@ -113,10 +123,12 @@ QString RoomSelector::getRoomPermissionDisplay(const ServerInfo_Room &room) */ QString roomPermissionDisplay = QString::fromStdString(room.privilegelevel()).toLower(); - if (QString::fromStdString(room.permissionlevel()).toLower() != "none") + if (QString::fromStdString(room.permissionlevel()).toLower() != "none") { roomPermissionDisplay = QString::fromStdString(room.permissionlevel()).toLower(); - if (roomPermissionDisplay == "") // catch all for misconfigured .ini room definitions + } + if (roomPermissionDisplay == "") { // catch all for misconfigured .ini room definitions roomPermissionDisplay = "none"; + } return roomPermissionDisplay; } @@ -124,8 +136,9 @@ QString RoomSelector::getRoomPermissionDisplay(const ServerInfo_Room &room) void RoomSelector::joinClicked() { QTreeWidgetItem *twi = roomList->currentItem(); - if (!twi) + if (!twi) { return; + } int id = twi->data(0, Qt::UserRole).toInt(); @@ -185,8 +198,9 @@ void TabServer::joinRoom(int id, bool setCurrent) return; } - if (setCurrent) + if (setCurrent) { tabSupervisor->setCurrentWidget((QWidget *)room); + } } void TabServer::joinRoomFinished(const Response &r, diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.h b/cockatrice/src/interface/widgets/tabs/tab_server.h index f2dd8f0a2..137823592 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.h +++ b/cockatrice/src/interface/widgets/tabs/tab_server.h @@ -1,8 +1,8 @@ /** * @file tab_server.h * @ingroup ServerTabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_SERVER_H #define TAB_SERVER_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index b10d615ff..e7075f78f 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -86,18 +86,22 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/) QStyleOption opt; opt.initFrom(this); opt.state |= QStyle::State_AutoRaise; - if (isEnabled() && underMouse() && !isChecked() && !isDown()) + if (isEnabled() && underMouse() && !isChecked() && !isDown()) { opt.state |= QStyle::State_Raised; - if (isChecked()) + } + if (isChecked()) { opt.state |= QStyle::State_On; - if (isDown()) + } + if (isDown()) { opt.state |= QStyle::State_Sunken; + } if (const auto *tb = qobject_cast(parent())) { int index = tb->currentIndex(); auto position = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tb); - if (tb->tabButton(index, position) == this) + if (tb->tabButton(index, position) == this) { opt.state |= QStyle::State_Selected; + } } style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); @@ -228,20 +232,25 @@ void TabSupervisor::retranslateUi() tabs.append(tabAccount); tabs.append(tabLog); QMapIterator roomIterator(roomTabs); - while (roomIterator.hasNext()) + while (roomIterator.hasNext()) { tabs.append(roomIterator.next().value()); + } QMapIterator gameIterator(gameTabs); - while (gameIterator.hasNext()) + while (gameIterator.hasNext()) { tabs.append(gameIterator.next().value()); + } QListIterator replayIterator(replayTabs); - while (replayIterator.hasNext()) + while (replayIterator.hasNext()) { tabs.append(replayIterator.next()); + } QListIterator deckEditorIterator(deckEditorTabs); - while (deckEditorIterator.hasNext()) + while (deckEditorIterator.hasNext()) { tabs.append(deckEditorIterator.next()); + } QMapIterator messageIterator(messageTabs); - while (messageIterator.hasNext()) + while (messageIterator.hasNext()) { tabs.append(messageIterator.next().value()); + } for (auto &tab : tabs) { if (tab) { @@ -448,9 +457,10 @@ void TabSupervisor::startLocal(const QList &_clients) isLocalGame = true; userInfo = new ServerInfo_User; localClients = _clients; - for (int i = 0; i < localClients.size(); ++i) + for (int i = 0; i < localClients.size(); ++i) { connect(localClients[i], &AbstractClient::gameEventContainerReceived, this, &TabSupervisor::processGameEventContainer); + } connect(localClients.first(), &AbstractClient::gameJoinedEventReceived, this, &TabSupervisor::localGameJoined); } @@ -459,8 +469,9 @@ void TabSupervisor::startLocal(const QList &_clients) */ void TabSupervisor::stop() { - if ((!client) && localClients.isEmpty()) + if ((!client) && localClients.isEmpty()) { return; + } resetTabsMenu(); @@ -694,10 +705,12 @@ void TabSupervisor::openTabLog() void TabSupervisor::updatePingTime(int value, int max) { - if (!tabServer) + if (!tabServer) { return; - if (tabServer->getContentsChanged()) + } + if (tabServer->getContentsChanged()) { return; + } setTabIcon(indexOf(tabServer), QIcon(PingPixmapGenerator::generatePixmap(15, value, max))); } @@ -706,12 +719,14 @@ void TabSupervisor::gameJoined(const Event_GameJoined &event) { QMap roomGameTypes; TabRoom *room = roomTabs.value(event.game_info().room_id()); - if (room) + if (room) { roomGameTypes = room->getGameTypes(); - else - for (int i = 0; i < event.game_types_size(); ++i) + } else { + for (int i = 0; i < event.game_types_size(); ++i) { roomGameTypes.insert(event.game_types(i).game_type_id(), QString::fromStdString(event.game_types(i).description())); + } + } auto *tab = new TabGame(this, QList() << client, event, roomGameTypes); connect(tab, &TabGame::gameClosing, this, &TabSupervisor::gameLeft); @@ -740,14 +755,16 @@ void TabSupervisor::localGameJoined(const Event_GameJoined &event) void TabSupervisor::gameLeft(TabGame *tab) { - if (tab == currentWidget()) + if (tab == currentWidget()) { emit setMenu(); + } gameTabs.remove(tab->getGame()->getGameMetaInfo()->gameId()); removeTab(indexOf(tab)); - if (!localClients.isEmpty()) + if (!localClients.isEmpty()) { stop(); + } } void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent) @@ -758,14 +775,16 @@ void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent) connect(tab, &TabRoom::openMessageDialog, this, &TabSupervisor::addMessageTab); myAddTab(tab); roomTabs.insert(info.room_id(), tab); - if (setCurrent) + if (setCurrent) { setCurrentWidget(tab); + } } void TabSupervisor::roomLeft(TabRoom *tab) { - if (tab == currentWidget()) + if (tab == currentWidget()) { emit setMenu(); + } roomTabs.remove(tab->getRoomId()); removeTab(indexOf(tab)); @@ -793,16 +812,18 @@ void TabSupervisor::openReplay(GameReplay *replay) void TabSupervisor::replayLeft(TabGame *tab) { - if (tab == currentWidget()) + if (tab == currentWidget()) { emit setMenu(); + } replayTabs.removeOne(tab); } TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus) { - if (receiverName == QString::fromStdString(userInfo->name())) + if (receiverName == QString::fromStdString(userInfo->name())) { return nullptr; + } ServerInfo_User otherUser; if (auto user = userListManager->getOnlineUser(receiverName)) { @@ -814,8 +835,9 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus TabMessage *tab; tab = messageTabs.value(QString::fromStdString(otherUser.name())); if (tab) { - if (focus) + if (focus) { setCurrentWidget(tab); + } return tab; } @@ -824,8 +846,9 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus connect(tab, &TabMessage::maximizeClient, this, &TabSupervisor::maximizeMainWindow); myAddTab(tab); messageTabs.insert(receiverName, tab); - if (focus) + if (focus) { setCurrentWidget(tab); + } return tab; } @@ -836,8 +859,9 @@ void TabSupervisor::maximizeMainWindow() void TabSupervisor::talkLeft(TabMessage *tab) { - if (tab == currentWidget()) + if (tab == currentWidget()) { emit setMenu(); + } messageTabs.remove(tab->getUserName()); removeTab(indexOf(tab)); @@ -934,8 +958,9 @@ TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCo void TabSupervisor::deckEditorClosed(AbstractTabDeckEditor *tab) { - if (tab == currentWidget()) + if (tab == currentWidget()) { emit setMenu(); + } deckEditorTabs.removeOne(tab); removeTab(indexOf(tab)); @@ -948,8 +973,9 @@ void TabSupervisor::tabUserEvent(bool globalEvent) tab->setContentsChanged(true); setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed")); } - if (globalEvent && SettingsCache::instance().getNotificationsEnabled()) + if (globalEvent && SettingsCache::instance().getNotificationsEnabled()) { QApplication::alert(this); + } } void TabSupervisor::updateTabText(Tab *tab, const QString &newTabText) @@ -962,39 +988,50 @@ void TabSupervisor::updateTabText(Tab *tab, const QString &newTabText) void TabSupervisor::processRoomEvent(const RoomEvent &event) { TabRoom *tab = roomTabs.value(event.room_id(), 0); - if (tab) + if (tab) { tab->processRoomEvent(event); + } } void TabSupervisor::processGameEventContainer(const GameEventContainer &cont) { TabGame *tab = gameTabs.value(cont.game_id()); - if (tab) + if (tab) { tab->getGame()->getGameEventHandler()->processGameEventContainer(cont, qobject_cast(sender()), {}); - else + } else { qCInfo(TabSupervisorLog) << "gameEvent: invalid gameId" << cont.game_id(); + } } void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) { QString senderName = QString::fromStdString(event.sender_name()); TabMessage *tab = messageTabs.value(senderName); - if (!tab) + if (!tab) { tab = messageTabs.value(QString::fromStdString(event.receiver_name())); + } if (!tab) { const ServerInfo_User *onlineUserInfo = userListManager->getOnlineUser(senderName); if (onlineUserInfo) { auto userLevel = UserLevelFlags(onlineUserInfo->user_level()); if (SettingsCache::instance().getIgnoreUnregisteredUserMessages() && - !userLevel.testFlag(ServerInfo_User::IsRegistered)) + !userLevel.testFlag(ServerInfo_User::IsRegistered)) { // Flags are additive, so reg/mod/admin are all IsRegistered return; + } else if (SettingsCache::instance().getIgnoreNonBuddyUserMessages() && + !userListManager->isUserBuddy(senderName) && !userLevel.testFlag(ServerInfo_User::IsModerator) && + !userLevel.testFlag(ServerInfo_User::IsAdmin)) { + // Ignore private messages from non-buddies + // Moderator/Admin messages are exempt to ensure warnings reach users + return; + } } tab = addMessageTab(QString::fromStdString(event.sender_name()), false); } - if (!tab) + if (!tab) { return; + } tab->processUserMessageEvent(event); } @@ -1012,8 +1049,9 @@ void TabSupervisor::actShowPopup(const QString &message) void TabSupervisor::processUserLeft(const QString &userName) { TabMessage *tab = messageTabs.value(userName); - if (tab) + if (tab) { tab->processUserLeft(); + } } void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined) @@ -1037,8 +1075,9 @@ void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined) } TabMessage *tab = messageTabs.value(userName); - if (tab) + if (tab) { tab->processUserJoined(userInfoJoined); + } } void TabSupervisor::updateCurrent(int index) @@ -1051,8 +1090,9 @@ void TabSupervisor::updateCurrent(int index) } emit setMenu(static_cast(widget(index))->getTabMenus()); tab->tabActivated(); - } else + } else { emit setMenu(); + } } /** @@ -1062,8 +1102,9 @@ void TabSupervisor::updateCurrent(int index) */ bool TabSupervisor::getAdminLocked() const { - if (!tabAdmin) + if (!tabAdmin) { return true; + } return tabAdmin->getLocked(); } @@ -1087,12 +1128,13 @@ void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event) tr("You have been promoted. Please log out and back in for changes to take effect.")); break; case Event_NotifyUser::WARNING: { - if (!QString::fromStdString(event.warning_reason()).simplified().isEmpty()) + if (!QString::fromStdString(event.warning_reason()).simplified().isEmpty()) { QMessageBox::warning(this, tr("Warned"), tr("You have received a warning due to %1.\nPlease refrain from engaging in this " "activity or further actions may be taken against you. If you have any " "questions, please private message a moderator.") .arg(QString::fromStdString(event.warning_reason()).simplified())); + } break; } case Event_NotifyUser::CUSTOM: { diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 0c4428f83..e77fb4f7b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -2,8 +2,8 @@ * @file tab_supervisor.h * @ingroup Core * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_SUPERVISOR_H #define TAB_SUPERVISOR_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp index 5e8fb8670..3112e7ada 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp @@ -1,14 +1,19 @@ #include "tab_visual_database_display.h" #include "tab_deck_editor.h" +#include "tab_supervisor.h" + +#include TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { - deckEditor = new TabDeckEditor(_tabSupervisor); - deckEditor->setHidden(true); - visualDatabaseDisplayWidget = new VisualDatabaseDisplayWidget( - this, deckEditor, deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseModel, - deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel); + auto databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); + databaseModel->setObjectName("databaseModel"); + + visualDatabaseDisplayWidget = new VisualDatabaseDisplayWidget(this, databaseModel); + + connect(visualDatabaseDisplayWidget, &VisualDatabaseDisplayWidget::edhrecRequested, this, + &TabVisualDatabaseDisplay::openEdhrecTab); setCentralWidget(visualDatabaseDisplayWidget); @@ -18,3 +23,8 @@ TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor void TabVisualDatabaseDisplay::retranslateUi() { } + +void TabVisualDatabaseDisplay::openEdhrecTab(const CardInfoPtr &info, bool isCommander) const +{ + getTabSupervisor()->addEdhrecTab(info, isCommander); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h index 66f38fb3d..3a4bcd6ea 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h @@ -1,8 +1,8 @@ /** * @file tab_visual_database_display.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_VISUAL_DATABASE_DISPLAY_H #define TAB_VISUAL_DATABASE_DISPLAY_H @@ -15,16 +15,18 @@ class TabVisualDatabaseDisplay : public Tab Q_OBJECT private: - TabDeckEditor *deckEditor; VisualDatabaseDisplayWidget *visualDatabaseDisplayWidget; +private slots: + void openEdhrecTab(const CardInfoPtr &info, bool isCommander) const; + public: TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor); void retranslateUi() override; [[nodiscard]] QString getTabText() const override { - return visualDatabaseDisplayWidget->displayModeButton->isChecked() ? tr("Database Display") - : tr("Visual Database Display"); + return visualDatabaseDisplayWidget->isVisualDisplayMode() ? tr("Visual Database Display") + : tr("Database Display"); } }; diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index d03ac483b..fd465ec21 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" +#include "../../cards/card_info_display_widget.h" #include "../../deck_editor/deck_state_manager.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +52,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra refreshShortcuts(); loadLayout(); + filterDockWidget->setHidden(true); cardDatabaseDockWidget->setHidden(true); } @@ -62,9 +65,10 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame = new QVBoxLayout; centralWidget->setLayout(centralFrame); - tabContainer = new TabDeckEditorVisualTabWidget( - centralWidget, this, deckStateManager->getModel(), cardDatabaseDockWidget->databaseDisplayWidget->databaseModel, - cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel); + auto databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); + databaseModel->setObjectName("databaseModel"); + + tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseModel); connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this, &TabDeckEditorVisual::changeModelIndexAndCardInfo); @@ -73,7 +77,14 @@ void TabDeckEditorVisual::createCentralFrame() connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClicked, this, &TabDeckEditorVisual::processMainboardCardClick); connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClickedDatabaseDisplay, this, - &TabDeckEditorVisual::processCardClickDatabaseDisplay); + &TabDeckEditorVisual::processDatabaseCardClick); + + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardAdded, this, &TabDeckEditorVisual::addCard); + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardDecremented, this, &TabDeckEditorVisual::decrementCard); + connect(tabContainer, &TabDeckEditorVisualTabWidget::edhrecRequested, this, &TabDeckEditorVisual::openEdhrecTab); + connect(tabContainer, &TabDeckEditorVisualTabWidget::printingSelectorRequested, this, + &TabDeckEditorVisual::showPrintingSelector); + connect(tabContainer, &TabDeckEditorVisualTabWidget::cardInfoRequested, this, &TabDeckEditorVisual::updateCardInfo); centralFrame->addWidget(tabContainer); setCentralWidget(centralWidget); @@ -99,7 +110,7 @@ void TabDeckEditorVisual::createMenus() registerDockWidget(viewMenu, cardInfoDockWidget, {250, 500}); registerDockWidget(viewMenu, deckDockWidget, {250, 360}); - registerDockWidget(viewMenu, filterDockWidget, {250, 250}); + // registerDockWidget(viewMenu, filterDockWidget, {250, 250}); registerDockWidget(viewMenu, printingSelectorDockWidget, {525, 250}); viewMenu->addSeparator(); @@ -116,8 +127,9 @@ void TabDeckEditorVisual::createMenus() QString TabDeckEditorVisual::getTabText() const { QString result = tr("Visual Deck: %1").arg(deckStateManager->getSimpleDeckName()); - if (deckStateManager->isModified()) + if (deckStateManager->isModified()) { result.prepend("* "); + } return result; } @@ -141,12 +153,10 @@ void TabDeckEditorVisual::changeModelIndexToCard(const ExactCard &activeCard) } } -void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance, +void TabDeckEditorVisual::processMainboardCardClick(const QMouseEvent *event, + const ExactCard &card, const QString &zoneName) { - auto card = instance->getCard(); - // Get the model index for the card QModelIndex idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); if (!idx.isValid()) { @@ -166,22 +176,14 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Alt + Right-click = decrement if (event->button() == Qt::RightButton && event->modifiers().testFlag(Qt::AltModifier)) { - if (zoneName == DECK_ZONE_MAIN) { - actDecrementCard(card); - } else { - actDecrementCardFromSideboard(card); - } + decrementCard(card, zoneName); // Keep selection intact. return; } // Alt + Left click = increment if (event->button() == Qt::LeftButton && event->modifiers().testFlag(Qt::AltModifier)) { - if (zoneName == DECK_ZONE_MAIN) { - actAddCard(card); - } else { - actAddCardToSideboard(card); - } + addCard(card, zoneName); // Keep selection intact. return; } @@ -217,13 +219,16 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, } /** @brief Handles clicks on cards in the database display. */ -void TabDeckEditorVisual::processCardClickDatabaseDisplay(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance) +void TabDeckEditorVisual::processDatabaseCardClick(const QMouseEvent *event, const ExactCard &card) { if (event->button() == Qt::LeftButton) { - actAddCard(instance->getCard()); + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + addCard(card, DECK_ZONE_SIDE); + } else { + addCard(card, DECK_ZONE_MAIN); + } } else if (event->button() == Qt::RightButton) { - actDecrementCard(instance->getCard()); + decrementCard(card, DECK_ZONE_MAIN); } else if (event->button() == Qt::MiddleButton) { deckDockWidget->actRemoveCard(); } @@ -238,14 +243,6 @@ bool TabDeckEditorVisual::actSaveDeckAs() return result; } -/** @brief Shows the printing selector dock and updates it with the current card. */ -void TabDeckEditorVisual::showPrintingSelector() -{ - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); - printingSelectorDockWidget->printingSelector->updateDisplay(); - printingSelectorDockWidget->setVisible(true); -} - /** @brief Refreshes keyboard shortcuts for this tab from settings. */ void TabDeckEditorVisual::refreshShortcuts() { @@ -275,18 +272,18 @@ void TabDeckEditorVisual::restartLayout() deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); - filterDockWidget->setVisible(false); + // filterDockWidget->setVisible(false); printingSelectorDockWidget->setVisible(true); setCentralWidget(centralWidget); addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); - addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); + // addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Vertical); splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal); - splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Horizontal); + // splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Horizontal); QTimer::singleShot(100, this, SLOT(freeDocksSize())); } @@ -298,13 +295,13 @@ void TabDeckEditorVisual::retranslateUi() cardInfoDockWidget->setWindowTitle(tr("Card Info")); deckDockWidget->setWindowTitle(tr("Deck")); - filterDockWidget->setWindowTitle(tr("Filters")); + // filterDockWidget->setWindowTitle(tr("Filters")); viewMenu->setTitle(tr("&View")); dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info")); dockToActions[deckDockWidget].menu->setTitle(tr("Deck")); - dockToActions[filterDockWidget].menu->setTitle(tr("Filters")); + // dockToActions[filterDockWidget].menu->setTitle(tr("Filters")); dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing")); for (auto &actions : dockToActions.values()) { diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h index 8a0677c9d..7d7a3f3a2 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -41,7 +41,7 @@ * - changeModelIndexAndCardInfo(const ExactCard &card) — Updates deck model selection and card info. * - changeModelIndexToCard(const ExactCard &card) — Selects the card in the deck view. * - processMainboardCardClick(QMouseEvent *event, ...) — Handles clicks on mainboard cards. - * - processCardClickDatabaseDisplay(QMouseEvent *event, ...) — Handles clicks on database cards. + * - processDatabaseCardClick(QMouseEvent *event, ...) — Handles clicks on database cards. * - actSaveDeckAs() — Overrides save action with temporary UI adjustments. * - showPrintingSelector() — Opens the printing selector dock for the current card. * - freeDocksSize() — Frees constraints on dock widget sizes. @@ -144,27 +144,20 @@ public slots: */ void onDeckChanged() override; - /** - * @brief Show the printing selector dock for the currently active card. - */ - void showPrintingSelector() override; - /** * @brief Handle card clicks in the mainboard visual deck. * @param event Mouse event triggering the action. - * @param instance Widget representing the clicked card. + * @param card The clicked card. * @param zoneName Deck zone of the card. */ - void processMainboardCardClick(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance, - const QString &zoneName); + void processMainboardCardClick(const QMouseEvent *event, const ExactCard &card, const QString &zoneName); /** * @brief Handle card clicks in the database visual display. * @param event Mouse event triggering the action. - * @param instance Widget representing the clicked card. + * @param card The clicked card. */ - void processCardClickDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void processDatabaseCardClick(const QMouseEvent *event, const ExactCard &card); /** * @brief Save the deck under a new name. diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp index 82aeb05a6..2ee560859 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp @@ -9,7 +9,6 @@ * @param _deckEditor Pointer to the associated deck editor. * @param _deckModel Pointer to the deck list model. * @param _cardDatabaseModel Pointer to the card database model. - * @param _cardDatabaseDisplayModel Pointer to the card database display model. * * Initializes all sub-widgets (visual deck view, database display, deck analytics, * sample hand) and sets up the tab layout and signal connections. @@ -17,10 +16,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, DeckListModel *_deckModel, - CardDatabaseModel *_cardDatabaseModel, - CardDatabaseDisplayModel *_cardDatabaseDisplayModel) - : QTabWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), cardDatabaseModel(_cardDatabaseModel), - cardDatabaseDisplayModel(_cardDatabaseDisplayModel) + CardDatabaseModel *_cardDatabaseModel) + : QTabWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), cardDatabaseModel(_cardDatabaseModel) { this->setTabsClosable(true); // Enable tab closing connect(this, &QTabWidget::tabCloseRequested, this, &TabDeckEditorVisualTabWidget::handleTabClose); @@ -34,16 +31,25 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, &TabDeckEditorVisualTabWidget::onCardChanged); connect(visualDeckView, &VisualDeckEditorWidget::cardClicked, this, &TabDeckEditorVisualTabWidget::onCardClickedDeckEditor); - connect(visualDeckView, &VisualDeckEditorWidget::cardAdditionRequested, deckEditor, - &AbstractTabDeckEditor::actAddCard); + connect(visualDeckView, &VisualDeckEditorWidget::cardAdditionRequested, this, + &TabDeckEditorVisualTabWidget::actAddCard); - visualDatabaseDisplay = - new VisualDatabaseDisplayWidget(this, deckEditor, _cardDatabaseModel, _cardDatabaseDisplayModel); + visualDatabaseDisplay = new VisualDatabaseDisplayWidget(this, _cardDatabaseModel, deckModel); visualDatabaseDisplay->setObjectName("visualDatabaseView"); connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardHoveredDatabaseDisplay, this, &TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay); connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this, &TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardAdded, this, + &TabDeckEditorVisualTabWidget::cardAdded); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardDecremented, this, + &TabDeckEditorVisualTabWidget::cardDecremented); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::edhrecRequested, this, + &TabDeckEditorVisualTabWidget::edhrecRequested); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::printingSelectorRequested, this, + &TabDeckEditorVisualTabWidget::printingSelectorRequested); + connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardInfoRequested, this, + &TabDeckEditorVisualTabWidget::cardInfoRequested); statsAnalyzer = new DeckListStatisticsAnalyzer(this, deckModel); statsAnalyzer->analyze(); @@ -57,6 +63,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display")); this->addNewTab(deckAnalytics, tr("Deck Analytics")); this->addNewTab(sampleHandWidget, tr("Sample Hand")); + + setTabsClosable(false); } /** @@ -80,25 +88,24 @@ void TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay(const ExactCard /** * @brief Emits the cardClicked signal when a card is clicked in the visual deck view. * @param event The mouse event. - * @param instance The widget instance of the clicked card. + * @param card The clicked card. * @param zoneName The zone of the deck where the card is located. */ void TabDeckEditorVisualTabWidget::onCardClickedDeckEditor(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance, - QString zoneName) + const ExactCard &card, + const QString &zoneName) { - emit cardClicked(event, instance, zoneName); + emit cardClicked(event, card, zoneName); } /** * @brief Emits the cardClickedDatabaseDisplay signal when a card is clicked in the database display. * @param event The mouse event. - * @param instance The widget instance of the clicked card. + * @param card The clicked card. */ -void TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance) +void TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay(QMouseEvent *event, const ExactCard &card) { - emit cardClickedDatabaseDisplay(event, instance); + emit cardClickedDatabaseDisplay(event, card); } /** @@ -164,3 +171,15 @@ void TabDeckEditorVisualTabWidget::handleTabClose(int index) this->removeTab(index); delete tab; } + +void TabDeckEditorVisualTabWidget::actAddCard(const ExactCard &card) +{ + QString zoneName; + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + zoneName = DECK_ZONE_SIDE; + } else { + zoneName = DECK_ZONE_MAIN; + } + + deckEditor->addCard(card, zoneName); +} diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h index 9468df425..2aabbb26a 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h @@ -55,27 +55,25 @@ public: * @param _deckEditor Pointer to the deck editor instance. * @param _deckModel Deck list model. * @param _cardDatabaseModel Card database model. - * @param _cardDatabaseDisplayModel Database display model. */ explicit TabDeckEditorVisualTabWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, DeckListModel *_deckModel, - CardDatabaseModel *_cardDatabaseModel, - CardDatabaseDisplayModel *_cardDatabaseDisplayModel); + CardDatabaseModel *_cardDatabaseModel); - /// Add a new tab with a widget and title. + /** @brief Add a new tab with a widget and title. */ void addNewTab(QWidget *widget, const QString &title); - /// Remove the currently active tab. + /** @brief Remove the currently active tab. */ void removeCurrentTab(); - /// Set the title of a specific tab. + /** @brief Set the title of a specific tab. */ void setTabTitle(int index, const QString &title); - /// Get the currently active tab widget. + /** @brief Get the currently active tab widget. */ [[nodiscard]] QWidget *getCurrentTab() const; - /// Get the total number of tabs. + /** @brief Get the total number of tabs. */ [[nodiscard]] int getTabCount() const; VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget. @@ -101,30 +99,35 @@ public slots: /** * @brief Emitted when a card is clicked in the deck view. * @param event Mouse event. - * @param instance Widget representing the clicked card. + * @param card The clicked card. * @param zoneName Deck zone of the card. */ - void onCardClickedDeckEditor(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void onCardClickedDeckEditor(QMouseEvent *event, const ExactCard &card, const QString &zoneName); /** * @brief Emitted when a card is clicked in the database display. * @param event Mouse event. - * @param instance Widget representing the clicked card. + * @param card The clicked card. */ - void onCardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void onCardClickedDatabaseDisplay(QMouseEvent *event, const ExactCard &card); signals: void cardChanged(const ExactCard &activeCard); void cardChangedDatabaseDisplay(const ExactCard &activeCard); - void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); - void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void cardClicked(QMouseEvent *event, const ExactCard &card, const QString &zoneName); + void cardClickedDatabaseDisplay(QMouseEvent *event, const ExactCard &card); + + void cardAdded(const ExactCard &card, const QString &zoneName); + void cardDecremented(const ExactCard &card, const QString &zoneName); + void edhrecRequested(const CardInfoPtr &cardInfo, bool isCommander); + void printingSelectorRequested(); + void cardInfoRequested(const ExactCard &cardName); private: - QVBoxLayout *layout; ///< Layout for tabs and controls. - AbstractTabDeckEditor *deckEditor; ///< Reference to the deck editor. - DeckListModel *deckModel; ///< Deck list model. - CardDatabaseModel *cardDatabaseModel; ///< Card database model. - CardDatabaseDisplayModel *cardDatabaseDisplayModel; ///< Card database display model. + QVBoxLayout *layout; ///< Layout for tabs and controls. + AbstractTabDeckEditor *deckEditor; ///< Reference to the deck editor. + DeckListModel *deckModel; ///< Deck list model. + CardDatabaseModel *cardDatabaseModel; ///< Card database model. private slots: /** @@ -132,6 +135,12 @@ private slots: * @param index Index of the tab to close. */ void handleTabClose(int index); + + /** + * @brief Adds card to maindeck or side depending on whether ctrl is held + * @param card + */ + void actAddCard(const ExactCard &card); }; #endif // TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h index ccf752e67..d3f64e23d 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h @@ -1,8 +1,8 @@ /** * @file tab_deck_storage_visual.h * @ingroup Tabs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef TAB_DECK_STORAGE_VISUAL_H #define TAB_DECK_STORAGE_VISUAL_H diff --git a/cockatrice/src/interface/widgets/utility/compact_push_button.cpp b/cockatrice/src/interface/widgets/utility/compact_push_button.cpp new file mode 100644 index 000000000..cf98bfede --- /dev/null +++ b/cockatrice/src/interface/widgets/utility/compact_push_button.cpp @@ -0,0 +1,73 @@ +#include "compact_push_button.h" + +CompactPushButton::CompactPushButton(QWidget *parent) : QPushButton(parent) +{ + setCheckable(true); + setFixedHeight(32); + + // default sizing + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + connect(this, &QPushButton::clicked, this, [] { + // your popup logic here + }); +} + +void CompactPushButton::setButtonText(const QString &text) +{ + fullText = text; + + if (!compact) { + setText(fullText); + } + + updateGeometryState(); +} + +void CompactPushButton::setButtonIcon(const QIcon &icon) +{ + setIcon(icon); +} + +void CompactPushButton::setCompact(bool enabled) +{ + compact = enabled; + + if (compact) { + setText(QString()); // icon only + } else { + setText(fullText); + } + + updateGeometryState(); +} + +void CompactPushButton::updateGeometryState() +{ + const int buttonHeight = 32; + + setMinimumHeight(buttonHeight); + setMaximumHeight(buttonHeight); + + if (compact) { + setMinimumWidth(buttonHeight); + setMaximumWidth(buttonHeight); + } else { + setMinimumWidth(0); + setMaximumWidth(QWIDGETSIZE_MAX); + } + + updateGeometry(); +} + +int CompactPushButton::expandedWidth() const +{ + QFontMetrics fm(font()); + + return fm.horizontalAdvance(fullText) + 48; // icon + padding +} + +int CompactPushButton::compactWidth() const +{ + return 32; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/utility/compact_push_button.h b/cockatrice/src/interface/widgets/utility/compact_push_button.h new file mode 100644 index 000000000..b0017beee --- /dev/null +++ b/cockatrice/src/interface/widgets/utility/compact_push_button.h @@ -0,0 +1,31 @@ +#ifndef COCKATRICE_COMPACT_PUSH_BUTTON_H +#define COCKATRICE_COMPACT_PUSH_BUTTON_H + +#include + +class CompactPushButton : public QPushButton +{ + Q_OBJECT + +public: + explicit CompactPushButton(QWidget *parent = nullptr); + + void setButtonText(const QString &text); + + void setButtonIcon(const QIcon &icon); + + void setCompact(bool enabled); + + int expandedWidth() const; + + int compactWidth() const; + +private: + void updateGeometryState(); + +private: + QString fullText; + bool compact = false; +}; + +#endif // COCKATRICE_COMPACT_PUSH_BUTTON_H diff --git a/cockatrice/src/interface/widgets/utility/custom_line_edit.cpp b/cockatrice/src/interface/widgets/utility/custom_line_edit.cpp index c137b4f35..05f56c275 100644 --- a/cockatrice/src/interface/widgets/utility/custom_line_edit.cpp +++ b/cockatrice/src/interface/widgets/utility/custom_line_edit.cpp @@ -24,14 +24,18 @@ bool LineEditUnfocusable::isUnfocusShortcut(QKeyEvent *event) QString modifier; QString keyNoMod; - if (event->modifiers() & Qt::ShiftModifier) + if (event->modifiers() & Qt::ShiftModifier) { modifier += "Shift+"; - if (event->modifiers() & Qt::ControlModifier) + } + if (event->modifiers() & Qt::ControlModifier) { modifier += "Ctrl+"; - if (event->modifiers() & Qt::AltModifier) + } + if (event->modifiers() & Qt::AltModifier) { modifier += "Alt+"; - if (event->modifiers() & Qt::MetaModifier) + } + if (event->modifiers() & Qt::MetaModifier) { modifier += "Meta+"; + } keyNoMod = QKeySequence(event->key()).toString(); @@ -39,8 +43,9 @@ bool LineEditUnfocusable::isUnfocusShortcut(QKeyEvent *event) QList unfocusShortcut = SettingsCache::instance().shortcuts().getShortcut("Player/unfocusTextBox"); for (const auto &unfocusKey : unfocusShortcut) { - if (key.matches(unfocusKey) == QKeySequence::ExactMatch) + if (key.matches(unfocusKey) == QKeySequence::ExactMatch) { return true; + } } return false; } @@ -79,10 +84,12 @@ void SearchLineEdit::keyPressEvent(QKeyEvent *event) static const QVector forwardWhenEmpty = {Qt::Key_Home, Qt::Key_End}; Qt::Key key = static_cast(event->key()); if (treeView) { - if (forwardToTreeView.contains(key)) + if (forwardToTreeView.contains(key)) { QCoreApplication::sendEvent(treeView, event); - if (text().isEmpty() && forwardWhenEmpty.contains(key)) + } + if (text().isEmpty() && forwardWhenEmpty.contains(key)) { QCoreApplication::sendEvent(treeView, event); + } } LineEditUnfocusable::keyPressEvent(event); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/utility/custom_line_edit.h b/cockatrice/src/interface/widgets/utility/custom_line_edit.h index 2026c1c1c..8c389b797 100644 --- a/cockatrice/src/interface/widgets/utility/custom_line_edit.h +++ b/cockatrice/src/interface/widgets/utility/custom_line_edit.h @@ -1,8 +1,8 @@ /** * @file custom_line_edit.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CUSTOMLINEEDIT_H #define CUSTOMLINEEDIT_H diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp index 389e69d57..13f475a61 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp @@ -46,8 +46,9 @@ void LineEditCompleter::keyPressEvent(QKeyEvent *event) int lastIndexof = qMax(0, textValue.lastIndexOf(" ")); QString finalString = textValue.left(lastIndexof); // Add a space if there's a word - if (finalString != "") + if (finalString != "") { finalString += " "; + } setText(finalString); return; } @@ -121,12 +122,14 @@ void LineEditCompleter::setCompleter(QCompleter *completer) void LineEditCompleter::setCompletionList(QStringList completionList) { - if (!c || c->popup()->isVisible()) + if (!c || c->popup()->isVisible()) { return; + } QStringListModel *model; model = (QStringListModel *)(c->model()); - if (model == NULL) + if (model == NULL) { model = new QStringListModel(); + } model->setStringList(completionList); } diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.h b/cockatrice/src/interface/widgets/utility/line_edit_completer.h index 7b3756e3c..65fa382ac 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.h +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.h @@ -1,8 +1,8 @@ /** * @file line_edit_completer.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LINEEDITCOMPLETER_H #define LINEEDITCOMPLETER_H diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.h b/cockatrice/src/interface/widgets/utility/sequence_edit.h index a5fe1a1c6..28e18d42f 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.h +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.h @@ -1,8 +1,8 @@ /** * @file sequence_edit.h * @ingroup UI - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SEQUENCEEDIT_H #define SEQUENCEEDIT_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h index 698ea9e97..497d25fce 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_color_filter_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H #define VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h new file mode 100644 index 000000000..5d9f7f944 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h @@ -0,0 +1,21 @@ +#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H +#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H + +#include + +const QString visualDatabaseDisplayFilterButtonStyle = QString(R"( + QPushButton { + background-color: palette(button); + color: palette(button-text); + padding: 5px 10px; + border-radius: 4px; + border: 1px solid palette(dark); + } + QPushButton:checked { + background-color: palette(highlight); + color: palette(highlighted-text); + border: 1px solid palette(shadow); + } +)"); + +#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 44b275afd..7ee57b1e9 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -57,8 +57,9 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi() void VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter() { QString filename = filenameInput->text().trimmed(); - if (filename.isEmpty()) + if (filename.isEmpty()) { return; + } QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename + ".json"; @@ -88,19 +89,22 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filena QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename; QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { return; + } QByteArray jsonData = file.readAll(); file.close(); QJsonDocument doc = QJsonDocument::fromJson(jsonData); - if (!doc.isObject()) + if (!doc.isObject()) { return; + } QJsonObject root = doc.object(); - if (!root.contains("filters") || !root["filters"].isArray()) + if (!root.contains("filters") || !root["filters"].isArray()) { return; + } QJsonArray filtersArray = root["filters"].toArray(); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h index bb5921a02..459633e42 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_filter_save_load_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H #define VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp index 3cc1bf23b..62e1bf5ba 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp @@ -1,20 +1,15 @@ #include "visual_database_display_filter_toolbar_widget.h" +#include "../deck_editor/card_database_view.h" #include "visual_database_display_widget.h" #include -VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent) - : QWidget(_parent), visualDatabaseDisplay(_parent) +VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent, + DeckListModel *deckListModel) + : FlowWidget(_parent, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff), + visualDatabaseDisplay(_parent), deckListModel(deckListModel) { - filterContainerLayout = new QHBoxLayout(this); - filterContainerLayout->setContentsMargins(11, 0, 11, 0); - filterContainerLayout->setSpacing(2); - setLayout(filterContainerLayout); - filterContainerLayout->setAlignment(Qt::AlignLeft); - - setMaximumHeight(80); - connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay, &VisualDatabaseDisplayWidget::onSearchModelChanged); @@ -101,11 +96,10 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize() filterLayout->setAlignment(Qt::AlignLeft); // create settings widgets - auto filterModel = visualDatabaseDisplay->filterModel; + auto filterModel = visualDatabaseDisplay->getFilterModel(); saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); - nameFilterWidget = - new VisualDatabaseDisplayNameFilterWidget(this, visualDatabaseDisplay->getDeckEditor(), filterModel); + nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, filterModel, deckListModel); mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel); subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); @@ -131,10 +125,18 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize() filterLayout->addWidget(quickFilterFormatLegalityWidget); // put everything into main layout - filterContainerLayout->addWidget(sortGroupBox); - filterContainerLayout->addWidget(filterGroupBox); - filterContainerLayout->addStretch(); - filterContainerLayout->addWidget(quickFilterSaveLoadWidget); + addWidget(sortGroupBox); + addWidget(filterGroupBox); + auto *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + spacer->setAttribute(Qt::WA_TransparentForMouseEvents); + addWidget(spacer); + addWidget(quickFilterSaveLoadWidget); + addWidget(quickFilterSaveLoadWidget); + + // Force a layout pass so sizeHint() is accurate + layout()->activate(); + fullWidthHint = sizeHint().width(); } void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi() @@ -155,4 +157,26 @@ void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi() quickFilterSubTypeWidget->setButtonText(tr("Sub Type")); quickFilterSetWidget->setButtonText(tr("Sets")); quickFilterFormatLegalityWidget->setButtonText(tr("Formats")); +} + +void VisualDatabaseDisplayFilterToolbarWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + updateCompactMode(event->size().width()); +} + +void VisualDatabaseDisplayFilterToolbarWidget::updateCompactMode(int availableWidth) +{ + const bool compact = availableWidth < fullWidthHint; + + const QList filterButtons = { + quickFilterSaveLoadWidget, quickFilterNameWidget, quickFilterMainTypeWidget, + quickFilterSubTypeWidget, quickFilterSetWidget, quickFilterFormatLegalityWidget, + }; + + for (auto *btn : filterButtons) { + if (btn->isCompact() != compact) { // only act on transitions + btn->setCompact(compact); + } + } } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h index 5cca5187a..8a3555455 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h @@ -10,7 +10,7 @@ class VisualDatabaseDisplayWidget; -class VisualDatabaseDisplayFilterToolbarWidget : public QWidget +class VisualDatabaseDisplayFilterToolbarWidget : public FlowWidget { Q_OBJECT @@ -18,12 +18,14 @@ signals: void searchModelChanged(); public: - explicit VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *parent); + explicit VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *parent, + DeckListModel *deckListModel = nullptr); void initialize(); void retranslateUi(); private: VisualDatabaseDisplayWidget *visualDatabaseDisplay; + DeckListModel *deckListModel; QGroupBox *sortGroupBox; QLabel *sortByLabel; @@ -32,7 +34,6 @@ private: QGroupBox *filterGroupBox; QLabel *filterByLabel; - QHBoxLayout *filterContainerLayout; SettingsButtonWidget *quickFilterSaveLoadWidget; VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget; SettingsButtonWidget *quickFilterNameWidget; @@ -45,6 +46,12 @@ private: VisualDatabaseDisplaySetFilterWidget *setFilterWidget; SettingsButtonWidget *quickFilterFormatLegalityWidget; VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget; + + int fullWidthHint = 0; + void updateCompactMode(int availableWidth); + +protected: + void resizeEvent(QResizeEvent *event) override; }; #endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp index 0df948016..633f07af7 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -1,6 +1,7 @@ #include "visual_database_display_format_legality_filter_widget.h" #include "../../../filters/filter_tree_model.h" +#include "visual_database_display_filter_button.h" #include #include @@ -80,8 +81,7 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons() for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" - "QPushButton:checked { background-color: green; color: white; }"); + button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); flowWidget->addWidget(button); formatButtons[it.key()] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index bc8e914bd..c44489c1b 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -1,6 +1,7 @@ #include "visual_database_display_main_type_filter_widget.h" #include "../../../filters/filter_tree_model.h" +#include "visual_database_display_filter_button.h" #include #include @@ -75,8 +76,8 @@ void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons() for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" - "QPushButton:checked { background-color: green; color: white; }"); + + button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); flowWidget->addWidget(button); typeButtons[it.key()] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index 9145812a7..6dec58319 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_main_type_filter_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H #define VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 5098696dd..3fa1a782a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -3,13 +3,14 @@ #include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" #include "../deck_editor/deck_state_manager.h" +#include "visual_database_display_filter_button.h" #include VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - FilterTreeModel *_filterModel) - : QWidget(parent), deckEditor(_deckEditor), filterModel(_filterModel) + FilterTreeModel *_filterModel, + DeckListModel *deckListModel) + : QWidget(parent), filterModel(_filterModel), deckListModel(deckListModel) { setMinimumWidth(300); setMaximumHeight(300); @@ -61,10 +62,9 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() { - DeckListModel *deckListModel = deckEditor->deckStateManager->getModel(); - - if (!deckListModel) + if (!deckListModel) { return; + } QList cardNames = deckListModel->getCardNames(); for (auto cardName : cardNames) { @@ -77,8 +77,9 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard() { DlgLoadDeckFromClipboard dlg(this); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } QStringList cardsInClipboard = dlg.getDeckList().getCardList(); for (QString cardName : cardsInClipboard) { @@ -90,13 +91,14 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard() void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name) { - if (activeFilters.contains(name)) + if (activeFilters.contains(name)) { return; + } // Create a button for the filter auto *button = new QPushButton(name, flowWidget); - button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" - "QPushButton:hover { background-color: red; color: white; }"); + + button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); connect(button, &QPushButton::clicked, this, [this, name]() { removeNameFilter(name); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h index 76d3ec29e..0c10408ae 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_name_filter_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H #define VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H @@ -21,8 +21,8 @@ class VisualDatabaseDisplayNameFilterWidget : public QWidget Q_OBJECT public: explicit VisualDatabaseDisplayNameFilterWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - FilterTreeModel *filterModel); + FilterTreeModel *filterModel, + DeckListModel *deckListModel = nullptr); void createNameFilter(const QString &name); void removeNameFilter(const QString &name); @@ -34,8 +34,8 @@ public slots: void retranslateUi(); private: - AbstractTabDeckEditor *deckEditor; FilterTreeModel *filterModel; + DeckListModel *deckListModel; QVBoxLayout *layout; QLineEdit *searchBox; FlowWidget *flowWidget; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 3339bc561..b72116461 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../filters/filter_tree_model.h" +#include "visual_database_display_filter_button.h" #include #include @@ -101,8 +102,8 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons() auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget); button->setCheckable(true); - button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" - "QPushButton:checked { background-color: green; color: white; }"); + + button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); flowWidget->addWidget(button); setButtons[shortName] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h index dc7fd0e92..bcc5e93c2 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_set_filter_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H #define VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 57559d12c..6d4bcb58e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -1,6 +1,7 @@ #include "visual_database_display_sub_type_filter_widget.h" #include "../../../filters/filter_tree_model.h" +#include "visual_database_display_filter_button.h" #include #include @@ -80,8 +81,8 @@ void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons() for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" - "QPushButton:checked { background-color: green; color: white; }"); + + button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); flowWidget->addWidget(button); typeButtons[it.key()] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index ce5546fc8..d713c7f99 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_sub_type_filter_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H #define VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index 44a9e98a0..cc4cce496 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -5,7 +5,7 @@ #include "../../../filters/syntax_help.h" #include "../../pixel_map_generator.h" #include "../cards/card_info_picture_with_text_overlay_widget.h" -#include "../quick_settings/settings_button_widget.h" +#include "../deck_editor/card_database_view.h" #include "../utility/custom_line_edit.h" #include "visual_database_display_color_filter_widget.h" #include "visual_database_display_filter_save_load_widget.h" @@ -23,12 +23,21 @@ #include VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, CardDatabaseModel *database_model, - CardDatabaseDisplayModel *database_display_model) - : QWidget(parent), deckEditor(_deckEditor), databaseModel(database_model), - databaseDisplayModel(database_display_model) + DeckListModel *deckListModel) + : QWidget(parent) { + debounceTimer = new QTimer(this); + debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout + + connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); + + // Create display model + databaseDisplayModel = new CardDatabaseDisplayModel(this); + databaseDisplayModel->setObjectName("databaseDisplayModel"); + databaseDisplayModel->setSourceModel(database_model); + databaseDisplayModel->setFilterKeyColumn(0); + cards = new QList; connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, &VisualDatabaseDisplayWidget::modelDirty); @@ -46,9 +55,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), &SettingsCache::setVisualDatabaseDisplayCardSize); - searchContainer = new QWidget(this); - searchLayout = new QHBoxLayout(searchContainer); - searchContainer->setLayout(searchLayout); + searchContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); searchEdit = new SearchLineEdit(); searchEdit->setObjectName("searchEdit"); @@ -57,7 +64,6 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); - searchEdit->installEventFilter(&searchKeySignals); setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); @@ -72,38 +78,29 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, filterModel = new FilterTreeModel(); filterModel->setObjectName("filterModel"); - searchKeySignals.setObjectName("searchKeySignals"); - connect(searchEdit, &SearchLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); + connect(searchEdit, &SearchLineEdit::textChanged, databaseDisplayModel, &CardDatabaseDisplayModel::setStringFilter); - DeckEditorDatabaseDisplayWidget *databaseDisplayWidget = deckEditor->cardDatabaseDockWidget->databaseDisplayWidget; - connect(&searchKeySignals, &KeySignals::onEnter, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlEnter, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlC, databaseDisplayWidget, - &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents); - connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); - - databaseView = databaseDisplayWidget->getDatabaseView(); + databaseView = new CardDatabaseView(this, databaseDisplayModel); + databaseView->setObjectName("databaseView"); databaseView->setFocusProxy(searchEdit); databaseView->setItemDelegate(nullptr); databaseView->setVisible(false); searchEdit->setTreeView(databaseView); + searchEdit->installEventFilter(databaseView->getKeySignals()); + + connect(databaseView, &CardDatabaseView::cardChanged, this, &VisualDatabaseDisplayWidget::onSelectedCardChanged); + connect(databaseView, &CardDatabaseView::cardAdded, this, &VisualDatabaseDisplayWidget::actAddCard); + connect(databaseView, &CardDatabaseView::cardDecremented, this, &VisualDatabaseDisplayWidget::actDecrementCard); + connect(databaseView, &CardDatabaseView::edhrecClicked, this, &VisualDatabaseDisplayWidget::edhrecRequested); + connect(databaseView, &CardDatabaseView::selectPrintingClicked, this, + &VisualDatabaseDisplayWidget::printingSelectorRequested); + connect(databaseView, &CardDatabaseView::relatedCardClicked, this, + &VisualDatabaseDisplayWidget::onRelatedCardClicked); colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel); - filterContainer = new VisualDatabaseDisplayFilterToolbarWidget(this); + filterContainer = new VisualDatabaseDisplayFilterToolbarWidget(this, deckListModel); clearFilterWidget = new QToolButton(); clearFilterWidget->setFixedSize(32, 32); @@ -132,6 +129,8 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, databaseLoadIndicator->setVisible(false); } + QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); + connect(scrollBar, &QScrollBar::valueChanged, [this](int /*value*/) { loadCurrentPage(); }); retranslateUi(); } @@ -140,11 +139,12 @@ void VisualDatabaseDisplayWidget::initialize() databaseLoadIndicator->setVisible(false); filterContainer->initialize(); + filterContainer->setVisible(true); - searchLayout->addWidget(colorFilterWidget); - searchLayout->addWidget(clearFilterWidget); - searchLayout->addWidget(searchEdit); - searchLayout->addWidget(displayModeButton); + searchContainer->addWidget(colorFilterWidget); + searchContainer->addWidget(clearFilterWidget); + searchContainer->addWidget(searchEdit); + searchContainer->addWidget(displayModeButton); mainLayout->addWidget(searchContainer); @@ -156,11 +156,6 @@ void VisualDatabaseDisplayWidget::initialize() mainLayout->addWidget(cardSizeWidget); - debounceTimer = new QTimer(this); - debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout - - connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); - databaseDisplayModel->setFilterTree(filterModel->filterTree()); connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); @@ -180,6 +175,11 @@ void VisualDatabaseDisplayWidget::retranslateUi() clearFilterWidget->setToolTip(tr("Clear all filters")); } +void VisualDatabaseDisplayWidget::highlightAllSearchEdit() +{ + searchEdit->setSelection(0, searchEdit->text().length()); +} + void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); @@ -205,9 +205,9 @@ void VisualDatabaseDisplayWidget::onDisplayModeChanged(bool checked) } } -void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance) +void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, const ExactCard &card) { - emit cardClickedDatabaseDisplay(event, instance); + emit cardClickedDatabaseDisplay(event, card); } void VisualDatabaseDisplayWidget::onHover(const ExactCard &hoveredCard) @@ -215,25 +215,21 @@ void VisualDatabaseDisplayWidget::onHover(const ExactCard &hoveredCard) emit cardHoveredDatabaseDisplay(hoveredCard); } -void VisualDatabaseDisplayWidget::addCard(const ExactCard &cardToAdd) +void VisualDatabaseDisplayWidget::addCardToDisplay(const ExactCard &cardToAdd) { cards->append(cardToAdd); auto *display = new CardInfoPictureWithTextOverlayWidget(flowWidget, false); display->setScaleFactor(cardSizeWidget->getSlider()->value()); display->setCard(cardToAdd); flowWidget->addWidget(display); - connect(display, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &VisualDatabaseDisplayWidget::onClick); + connect(display, &CardInfoPictureWithTextOverlayWidget::cardClicked, this, &VisualDatabaseDisplayWidget::onClick); connect(display, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &VisualDatabaseDisplayWidget::onHover); connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor); } -void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const +bool VisualDatabaseDisplayWidget::isVisualDisplayMode() const { - databaseDisplayModel->setStringFilter(search); - QModelIndexList sel = databaseView->selectionModel()->selectedRows(); - if (sel.isEmpty() && databaseDisplayModel->rowCount()) - databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), - QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + return !displayModeButton->isChecked(); } void VisualDatabaseDisplayWidget::onSearchModelChanged() @@ -243,9 +239,8 @@ void VisualDatabaseDisplayWidget::onSearchModelChanged() flowWidget->clearLayout(); // Clear existing cards cards->clear(); // Clear the card list // Reset scrollbar position to the top after loading new cards - if (QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar()) { - scrollBar->setValue(0); // Reset scrollbar to top - } + QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); + scrollBar->setValue(0); // Reset scrollbar to top currentPage = 0; loadCurrentPage(); @@ -253,6 +248,44 @@ void VisualDatabaseDisplayWidget::onSearchModelChanged() } } +void VisualDatabaseDisplayWidget::onSelectedCardChanged(const QString &cardName) +{ + emit cardHoveredDatabaseDisplay(CardDatabaseManager::query()->getPreferredCard(cardName)); +} + +void VisualDatabaseDisplayWidget::actAddCard(const QString &cardName, const QString &zoneName) +{ + highlightAllSearchEdit(); + ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName); + emit cardAdded(exactCard, zoneName); +} + +void VisualDatabaseDisplayWidget::actDecrementCard(const QString &cardName, const QString &zoneName) +{ + ExactCard exactCard = CardDatabaseManager::query()->getPreferredCard(cardName); + emit cardDecremented(exactCard, zoneName); +} + +void VisualDatabaseDisplayWidget::onRelatedCardClicked(const QString &relatedCard) +{ + ExactCard exactCard = CardDatabaseManager::query()->guessCard({relatedCard}); + emit cardInfoRequested(exactCard); +} + +bool VisualDatabaseDisplayWidget::nearEndOfPage() const +{ + if (!flowWidget->isVisible()) { + return false; + } + + QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); + if (scrollBar->value() + scrollBar->pageStep() * 2 < scrollBar->maximum()) { + return false; // there is at least two pages of space to scroll remaining + } + + return true; +} + void VisualDatabaseDisplayWidget::loadCurrentPage() { // Ensure only the initial page is loaded @@ -260,7 +293,7 @@ void VisualDatabaseDisplayWidget::loadCurrentPage() // Only load the first page initially qCDebug(VisualDatabaseDisplayLog) << "Loading the first page"; populateCards(); - } else { + } else if (nearEndOfPage()) { // If not the first page, just load the next page and append to the flow widget loadNextPage(); } @@ -285,7 +318,9 @@ void VisualDatabaseDisplayWidget::loadNextPage() } // Load the next page of cards and add them to the flow widget + flowWidget->setUpdatesEnabled(false); loadPage(start, end); + flowWidget->setUpdatesEnabled(true); } void VisualDatabaseDisplayWidget::loadPage(int start, int end) @@ -303,12 +338,12 @@ void VisualDatabaseDisplayWidget::loadPage(int start, int end) for (const CardFilter *setFilter : setFilters) { if (setMap.contains(setFilter->term())) { for (PrintingInfo printing : setMap[setFilter->term()]) { - addCard(ExactCard(info, printing)); + addCardToDisplay(ExactCard(info, printing)); } } } } else { - addCard(CardDatabaseManager::query()->getPreferredCard(info)); + addCardToDisplay(CardDatabaseManager::query()->getPreferredCard(info)); } } else { qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!"; @@ -337,23 +372,3 @@ void VisualDatabaseDisplayWidget::databaseDataChanged(const QModelIndex &topLeft (void)bottomRight; qCDebug(VisualDatabaseDisplayLog) << "Database Data changed"; } - -void VisualDatabaseDisplayWidget::wheelEvent(QWheelEvent *event) -{ - int totalCards = databaseDisplayModel->rowCount(); // Total number of cards - int loadedCards = currentPage * cardsPerPage; - - // Handle scrolling down - if (event->angleDelta().y() < 0) { - // Check if the next page has any cards to load - if (loadedCards < totalCards) { - loadCurrentPage(); // Load the next page - event->accept(); // Accept the event as valid - return; - } - qCDebug(VisualDatabaseDisplayLog) << loadedCards << ":" << totalCards; - } - - // Prevent overscrolling when there's no more data to load - event->ignore(); -} diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index 3aa8d7f8e..a383e8ead 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_display_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_DISPLAY_WIDGET_H #define VISUAL_DATABASE_DISPLAY_WIDGET_H @@ -34,9 +34,8 @@ class VisualDatabaseDisplayWidget : public QWidget public: explicit VisualDatabaseDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, CardDatabaseModel *database_model, - CardDatabaseDisplayModel *database_display_model); + DeckListModel *deckListModel = nullptr); void retranslateUi(); void adjustCardsPerPage(); @@ -47,56 +46,66 @@ public: void sortCardList(const QStringList &properties, Qt::SortOrder order) const; void setDeckList(const DeckList &new_deck_list_model); - AbstractTabDeckEditor *getDeckEditor() - { - return deckEditor; - } - CardDatabaseDisplayModel *getDatabaseDisplayModel() { return databaseDisplayModel; } - QTreeView *getDatabaseView() + CardDatabaseView *getDatabaseView() { return databaseView; } - QWidget *searchContainer; - QHBoxLayout *searchLayout; - SearchLineEdit *searchEdit; - QPushButton *displayModeButton; - FilterTreeModel *filterModel; - VisualDatabaseDisplayColorFilterWidget *colorFilterWidget; + FilterTreeModel *getFilterModel() + { + return filterModel; + } + + /** + * @return False if the widget is in database display mode and true if it's in visual display mode + */ + bool isVisualDisplayMode() const; public slots: void onSearchModelChanged(); signals: - void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void cardClickedDatabaseDisplay(QMouseEvent *event, const ExactCard &card); void cardHoveredDatabaseDisplay(const ExactCard &hoveredCard); + void cardAdded(const ExactCard &card, const QString &zoneName); + void cardDecremented(const ExactCard &card, const QString &zoneName); + void edhrecRequested(const CardInfoPtr &cardInfo, bool isCommander); + void printingSelectorRequested(); + void cardInfoRequested(const ExactCard &cardName); + protected slots: void initialize(); - void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); + void onClick(QMouseEvent *event, const ExactCard &card); void onHover(const ExactCard &hoveredCard); - void addCard(const ExactCard &cardToAdd); + void addCardToDisplay(const ExactCard &cardToAdd); void databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void wheelEvent(QWheelEvent *event) override; void modelDirty() const; - void updateSearch(const QString &search) const; void onDisplayModeChanged(bool checked); + void onSelectedCardChanged(const QString &cardName); + void actAddCard(const QString &cardName, const QString &zoneName); + void actDecrementCard(const QString &cardName, const QString &zoneName); + void onRelatedCardClicked(const QString &relatedCard); + private: + FlowWidget *searchContainer; + SearchLineEdit *searchEdit; + QPushButton *displayModeButton; + FilterTreeModel *filterModel; + VisualDatabaseDisplayColorFilterWidget *colorFilterWidget; + QLabel *databaseLoadIndicator; QToolButton *clearFilterWidget; VisualDatabaseDisplayFilterToolbarWidget *filterContainer; - KeySignals searchKeySignals; - AbstractTabDeckEditor *deckEditor; - CardDatabaseModel *databaseModel; CardDatabaseDisplayModel *databaseDisplayModel; - QTreeView *databaseView; + CardDatabaseView *databaseView; QList *cards; QVBoxLayout *mainLayout; QScrollArea *scrollArea; @@ -112,6 +121,9 @@ private: int currentPage = 0; // Current page index int cardsPerPage = 100; // Number of cards per page + void highlightAllSearchEdit(); + bool nearEndOfPage() const; + protected: void resizeEvent(QResizeEvent *event) override; }; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.h index 05bf43118..5fe29fa2d 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.h @@ -1,8 +1,8 @@ /** * @file visual_database_filter_display_widget.h * @ingroup VisualCardDatabaseWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H #define VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp index 79a98fda6..f44c9c3ef 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp @@ -72,7 +72,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) sortCriteriaButton->addSettingsWidget(sortLabel); sortCriteriaButton->addSettingsWidget(sortByListWidget); - displayTypeButton = new QPushButton(this); + displayTypeButton = new CompactPushButton(this); connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckDisplayOptionsWidget::updateDisplayType); groupAndSortLayout->addWidget(groupByLabel); @@ -91,7 +91,8 @@ void VisualDeckDisplayOptionsWidget::retranslateUi() sortByLabel->setText(tr("Sort by:")); sortLabel->setText(tr("Click and drag to change the sort order within the groups")); sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setText(tr("Toggle Layout: Overlap")); + displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales")); displayTypeButton->setToolTip( tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } @@ -115,11 +116,32 @@ void VisualDeckDisplayOptionsWidget::updateDisplayType() // Update UI and emit signal switch (currentDisplayType) { case DisplayType::Flat: - displayTypeButton->setText(tr("Toggle Layout: Flat")); + displayTypeButton->setButtonText(tr("Toggle Layout: Flat")); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scroll")); break; case DisplayType::Overlap: - displayTypeButton->setText(tr("Toggle Layout: Overlap")); + displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales")); break; } emit displayTypeChanged(currentDisplayType); } + +void VisualDeckDisplayOptionsWidget::updateCompactMode(bool mode) +{ + displayTypeButton->setCompact(mode); +} + +int VisualDeckDisplayOptionsWidget::expandedWidth() const +{ + return groupByLabel->sizeHint().width() + groupByComboBox->sizeHint().width() + sortByLabel->sizeHint().width() + + sortCriteriaButton->sizeHint().width() + displayTypeButton->expandedWidth() + + (groupAndSortLayout->spacing() * 4); +} + +int VisualDeckDisplayOptionsWidget::compactWidth() const +{ + return groupByLabel->sizeHint().width() + groupByComboBox->sizeHint().width() + sortByLabel->sizeHint().width() + + sortCriteriaButton->sizeHint().width() + displayTypeButton->compactWidth() + + (groupAndSortLayout->spacing() * 4); +} diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h index 7a447753f..bc11884df 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h @@ -53,6 +53,9 @@ public slots: * Called when the application language changes. */ void retranslateUi(); + void updateCompactMode(bool mode); + int expandedWidth() const; + int compactWidth() const; public: /** @@ -101,37 +104,37 @@ private slots: void updateDisplayType(); private: - /// Layout for grouping and sorting UI elements. + /** @brief Layout for grouping and sorting UI elements. */ QHBoxLayout *groupAndSortLayout; - /// Current deck display type. + /** @brief Current deck display type. */ DisplayType currentDisplayType = DisplayType::Overlap; - /// Button used to toggle the display layout. - QPushButton *displayTypeButton; + /** @brief Button used to toggle the display layout. */ + CompactPushButton *displayTypeButton; - /// Label for the group-by selector. + /** @brief Label for the group-by selector. */ QLabel *groupByLabel; - /// Combo box listing group-by criteria. + /** @brief Combo box listing group-by criteria. */ QComboBox *groupByComboBox; - /// Currently active group-by criterion. + /** @brief Currently active group-by criterion. */ QString activeGroupCriteria = "maintype"; - /// Encapsulates the sort settings widgets (label + list). + /** @brief Encapsulates the sort settings widgets (label + list). */ SettingsButtonWidget *sortCriteriaButton; - /// Label for “Sort by”. + /** @brief Label for "Sort by". */ QLabel *sortByLabel; - /// Descriptive label inside the sort criteria button. + /** @brief Descriptive label inside the sort criteria button. */ QLabel *sortLabel; - /// Draggable list of sort criteria. + /** @brief Draggable list of sort criteria. */ QListWidget *sortByListWidget; - /// Ordered list of current sort criteria. + /** @brief Ordered list of current sort criteria. */ QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; }; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index cc35372b0..8417ffa34 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -112,13 +112,15 @@ static QList cardNodesToExactCards(QList no QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) { QList randomCards; - if (!deckListModel) + if (!deckListModel) { return randomCards; + } QList mainDeckCards = cardNodesToExactCards(deckListModel->getCardNodesForZone(DECK_ZONE_MAIN)); - if (mainDeckCards.isEmpty()) + if (mainDeckCards.isEmpty()) { return randomCards; + } // Shuffle the deck std::random_device rd; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h index c63c74a4d..c6c07dd55 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_editor_sample_hand_widget.h * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H #define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index e957eb304..815892f4c 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -9,6 +9,7 @@ #include "../general/layout_containers/flow_widget.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h" +#include "../utility/compact_push_button.h" #include "visual_deck_display_options_widget.h" #include @@ -69,10 +70,17 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, void VisualDeckEditorWidget::initializeSearchBarAndCompleter() { - searchBar = new QLineEdit(this); + searchContainer = new QWidget(this); + searchContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + searchLayout = new QHBoxLayout(searchContainer); + searchContainer->setLayout(searchLayout); + + searchBar = new QLineEdit(searchContainer); + searchContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() { - if (!searchBar->hasFocus()) + if (!searchBar->hasFocus()) { return; + } ExactCard card = CardDatabaseManager::query()->getCard({searchBar->text()}); if (card) { @@ -80,6 +88,8 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter() } }); + searchLayout->addWidget(searchBar); + setFocusProxy(searchBar); setFocusPolicy(Qt::ClickFocus); @@ -133,13 +143,16 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter() }); // Search button functionality - searchPushButton = new QPushButton(this); + searchPushButton = new CompactPushButton(searchContainer); + searchPushButton->setButtonIcon(QPixmap("theme:icons/search")); connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { ExactCard card = CardDatabaseManager::query()->getCard({searchBar->text()}); if (card) { emit cardAdditionRequested(card); } }); + + searchLayout->addWidget(searchPushButton); } void VisualDeckEditorWidget::initializeDisplayOptionsWidget() @@ -156,18 +169,14 @@ void VisualDeckEditorWidget::initializeDisplayOptionsWidget() void VisualDeckEditorWidget::initializeDisplayOptionsAndSearchWidget() { initializeSearchBarAndCompleter(); - initializeDisplayOptionsWidget(); - displayOptionsAndSearch = new QWidget(this); - displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); - displayOptionsAndSearchLayout->setContentsMargins(0, 0, 0, 0); - displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); - displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); + displayOptionsAndSearch = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); - displayOptionsAndSearchLayout->addWidget(displayOptionsWidget); - displayOptionsAndSearchLayout->addWidget(searchBar); - displayOptionsAndSearchLayout->addWidget(searchPushButton); + // We split into two sub-widgets here so that the searchBar and button wrap together. At this point, we've done + // pretty much all we can and have reached our minimum size. + displayOptionsAndSearch->addWidget(displayOptionsWidget); + displayOptionsAndSearch->addWidget(searchContainer); // Expanding — fills remainder of its row } void VisualDeckEditorWidget::initializeScrollAreaAndZoneContainer() @@ -205,7 +214,7 @@ void VisualDeckEditorWidget::connectDeckListModel() void VisualDeckEditorWidget::retranslateUi() { searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database...")); - searchPushButton->setText(tr("Quick search and add card")); + searchPushButton->setButtonText(tr("Quick search and add card")); searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); @@ -214,6 +223,44 @@ void VisualDeckEditorWidget::retranslateUi() } } +void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + updateCompactMode(); +} + +void VisualDeckEditorWidget::updateCompactMode() +{ + const int spacing = displayOptionsAndSearch->layout()->spacing(); + + const int available = displayOptionsAndSearch->width(); + + const int searchExpanded = + searchBar->sizeHint().width() + searchPushButton->expandedWidth() + searchLayout->spacing(); + + const int fullWidth = displayOptionsWidget->expandedWidth() + spacing + searchExpanded; + + const int displayCompactWidth = displayOptionsWidget->compactWidth() + spacing + searchExpanded; + + // everything expanded + if (available >= fullWidth) { + displayOptionsWidget->updateCompactMode(false); + searchPushButton->setCompact(false); + return; + } + + // only display compact + if (available >= displayCompactWidth) { + displayOptionsWidget->updateCompactMode(true); + searchPushButton->setCompact(false); + return; + } + + // both compact + displayOptionsWidget->updateCompactMode(true); + searchPushButton->setCompact(true); +} + void VisualDeckEditorWidget::updatePlaceholderVisibility() { if (placeholderWidget) { @@ -234,7 +281,7 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(), displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::cardClicked); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, &VisualDeckEditorWidget::cleanupInvalidZones); connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, @@ -354,13 +401,6 @@ void VisualDeckEditorWidget::decklistDataChanged(QModelIndex topLeft, QModelInde // User Interaction // ===================================================================================================================== -void VisualDeckEditorWidget::onCardClick(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance, - QString zoneName) -{ - emit cardClicked(event, instance, zoneName); -} - void VisualDeckEditorWidget::onHover(const ExactCard &hoveredCard) { // If user has any card selected, ignore hover @@ -371,7 +411,7 @@ void VisualDeckEditorWidget::onHover(const ExactCard &hoveredCard) // If nothing is selected -> this is our "active/preview" card emit activeCardChanged(hoveredCard); - // TODO: highlight hovered card visually: + //! \todo Highlight hovered card visually. // highlightHoveredCard(hoveredCard); } @@ -382,7 +422,7 @@ void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) } if (selectionModel) { - // TODO: Possibly disconnect old ones? + //! \todo Possibly disconnect old signal connections. } selectionModel = model; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 13065d623..da02b5c1f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_editor_widget.h * @ingroup DeckEditors - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_EDITOR_H #define VISUAL_DECK_EDITOR_H @@ -11,6 +11,7 @@ #include "../cards/card_size_widget.h" #include "../general/layout_containers/overlap_control_widget.h" #include "../quick_settings/settings_button_widget.h" +#include "../utility/compact_push_button.h" #include "visual_deck_editor_placeholder_widget.h" #include @@ -39,6 +40,7 @@ class VisualDeckEditorWidget : public QWidget public: explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel, QItemSelectionModel *selectionModel); void retranslateUi(); + void updateCompactMode(); void clearAllDisplayWidgets(); void setDeckList(const DeckList &_deckListModel); @@ -67,7 +69,7 @@ signals: void activeCardChanged(const ExactCard &activeCard); void activeGroupCriteriaChanged(QString activeGroupCriteria); void activeSortCriteriaChanged(QStringList activeSortCriteria); - void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void cardClicked(QMouseEvent *event, const ExactCard &card, const QString &zoneName); void cardAdditionRequested(const ExactCard &card); void displayTypeChanged(DisplayType displayType); @@ -80,21 +82,24 @@ protected: protected slots: void onHover(const ExactCard &hoveredCard); - void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); void decklistModelReset(); + void resizeEvent(QResizeEvent *event) override; private: + int expandedWidthAll = -1; + int expandedWidthDisplayCompact = -1; DeckListModel *deckListModel; QItemSelectionModel *selectionModel; QVBoxLayout *mainLayout; CardDatabaseModel *cardDatabaseModel; CardDatabaseDisplayModel *cardDatabaseDisplayModel; CardCompleterProxyModel *proxyModel; + QWidget *searchContainer; + QHBoxLayout *searchLayout; QCompleter *completer; - QWidget *displayOptionsAndSearch; - QHBoxLayout *displayOptionsAndSearchLayout; + FlowWidget *displayOptionsAndSearch; VisualDeckDisplayOptionsWidget *displayOptionsWidget; - QPushButton *searchPushButton; + CompactPushButton *searchPushButton; QScrollArea *scrollArea; QWidget *zoneContainer; QVBoxLayout *zoneContainerLayout; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h index 551dbb35c..0207e2ee2 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_color_identity_filter_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H #define DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index bfd0a170d..4bd7915cd 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_deck_tags_display_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H #define DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h index c0fa86d19..7a3a735a2 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_tag_addition_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_TAG_ADDITION_WIDGET_H #define DECK_PREVIEW_TAG_ADDITION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h index 59b330a65..4d9040af4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h @@ -2,8 +2,8 @@ * @file deck_preview_tag_dialog.h * @ingroup VisualDeckPreviewWidgets * @ingroup Dialogs - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_TAG_DIALOG_H #define DECK_PREVIEW_TAG_DIALOG_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h index a868aa4f1..980741c5d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_tag_display_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_TAG_DISPLAY_WIDGET_H #define DECK_PREVIEW_TAG_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h index 5caae90a1..a3301799a 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_tag_item_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_TAG_ITEM_WIDGET_H #define DECK_PREVIEW_TAG_ITEM_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 77ea8f865..06dc9dd84 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -28,8 +28,8 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, deckLoader = new DeckLoader(this); connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi); - /* TODO: We shouldn't update the tags on *every* deck load, since it's kinda expensive. We should instead count how - many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */ + //! \todo Batch tag refresh: count finished deck loads and refresh tags once all decks are loaded. + // Currently expensive: refreshes on each individual deck load instead of once at the end. connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget, &VisualDeckStorageTagFilterWidget::refreshTags); deckLoader->loadFromFileAsync(filePath, DeckFileFormat::getFormatFromName(filePath), false); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 98116cabe..0ed64e9e2 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -1,8 +1,8 @@ /** * @file deck_preview_widget.h * @ingroup VisualDeckPreviewWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DECK_PREVIEW_WIDGET_H #define DECK_PREVIEW_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h index d76fb0497..a5e3be212 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_storage_folder_display_widget.h * @ingroup VisualDeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H #define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h index 67d260b21..2f3d81aeb 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_storage_search_widget.h * @ingroup VisualDeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H #define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h index 1ea06aba4..24eddba33 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_storage_sort_widget.h * @ingroup VisualDeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_STORAGE_SORT_WIDGET_H #define VISUAL_DECK_STORAGE_SORT_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp index 28fd7a5ca..c4c8d18a8 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -126,8 +126,9 @@ void VisualDeckStorageTagFilterWidget::addTagIfNotPresent(const QString &tag) void VisualDeckStorageTagFilterWidget::sortTags() { auto *flowWidget = findChild(); - if (!flowWidget) + if (!flowWidget) { return; + } // Get all tag widgets QList tagWidgets = findChildren(); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h index ada94a244..3290c9e9a 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_storage_tag_filter_widget.h * @ingroup VisualDeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H #define VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index b13c51700..c3c0ae91b 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -1,8 +1,8 @@ /** * @file visual_deck_storage_widget.h * @ingroup VisualDeckStorageWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef VISUAL_DECK_STORAGE_WIDGET_H #define VISUAL_DECK_STORAGE_WIDGET_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 86e6c1534..69d3260bc 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -22,14 +22,9 @@ #include "../client/network/update/client/client_update_checker.h" #include "../client/network/update/client/release_channel.h" #include "../client/settings/cache_settings.h" -#include "../interface/widgets/dialogs/dlg_connect.h" #include "../interface/widgets/dialogs/dlg_edit_tokens.h" -#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" -#include "../interface/widgets/dialogs/dlg_forgot_password_request.h" -#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" #include "../interface/widgets/dialogs/dlg_local_game_options.h" #include "../interface/widgets/dialogs/dlg_manage_sets.h" -#include "../interface/widgets/dialogs/dlg_register.h" #include "../interface/widgets/dialogs/dlg_settings.h" #include "../interface/widgets/dialogs/dlg_startup_card_check.h" #include "../interface/widgets/dialogs/dlg_tip_of_the_day.h" @@ -40,6 +35,8 @@ #include "../main.h" #include "logger.h" #include "version_string.h" +#include "widgets/dialogs/dlg_connect.h" +#include "widgets/server/handle_public_servers.h" #include "widgets/utility/get_text_with_max.h" #include @@ -67,8 +64,6 @@ #include #include #include -#include -#include #include #include @@ -93,66 +88,18 @@ inline Q_LOGGING_CATEGORY(MainWindowLog, "main_window"); */ void MainWindow::updateTabMenu(const QList &newMenuList) { - for (auto &tabMenu : tabMenus) + for (auto &tabMenu : tabMenus) { menuBar()->removeAction(tabMenu->menuAction()); - tabMenus = newMenuList; - for (auto &tabMenu : tabMenus) - menuBar()->insertMenu(helpMenu->menuAction(), tabMenu); -} - -void MainWindow::processConnectionClosedEvent(const Event_ConnectionClosed &event) -{ - client->disconnectFromServer(); - QString reasonStr; - switch (event.reason()) { - case Event_ConnectionClosed::USER_LIMIT_REACHED: - reasonStr = tr("The server has reached its maximum user capacity, please check back later."); - break; - case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: - reasonStr = tr("There are too many concurrent connections from your address."); - break; - case Event_ConnectionClosed::BANNED: { - reasonStr = tr("Banned by moderator"); - if (event.has_end_time()) - reasonStr.append( - "\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString())); - else - reasonStr.append("\n" + tr("This ban lasts indefinitely.")); - if (event.has_reason_str()) - reasonStr.append("\n\n" + QString::fromStdString(event.reason_str())); - break; - } - case Event_ConnectionClosed::SERVER_SHUTDOWN: - reasonStr = tr("Scheduled server shutdown."); - break; - case Event_ConnectionClosed::USERNAMEINVALID: - reasonStr = tr("Invalid username."); - break; - case Event_ConnectionClosed::LOGGEDINELSEWERE: - reasonStr = tr("You have been logged out due to logging in at another location."); - break; - default: - reasonStr = QString::fromStdString(event.reason_str()); } - QMessageBox::critical(this, tr("Connection closed"), - tr("The server has terminated your connection.\nReason: %1").arg(reasonStr)); -} - -void MainWindow::processServerShutdownEvent(const Event_ServerShutdown &event) -{ - serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running " - "games will be lost.\nReason for shutdown: %1", - "", event.minutes()) - .arg(QString::fromStdString(event.reason()))); - serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64)); - serverShutdownMessageBox.setText(tr("Scheduled server shutdown")); - serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal); - serverShutdownMessageBox.setVisible(true); + tabMenus = newMenuList; + for (auto &tabMenu : tabMenus) { + menuBar()->insertMenu(helpMenu->menuAction(), tabMenu); + } } void MainWindow::statusChanged(ClientStatus _status) { - setClientStatusTitle(); + connectionController->refreshWindowTitle(); switch (_status) { case StatusDisconnected: tabSupervisor->stop(); @@ -177,51 +124,16 @@ void MainWindow::statusChanged(ClientStatus _status) } } -void MainWindow::userInfoReceived(const ServerInfo_User &info) -{ - tabSupervisor->start(info); -} - -void MainWindow::registerAccepted() -{ - QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nWill now login.")); -} - -void MainWindow::registerAcceptedNeedsActivate() -{ - // nothing -} - -void MainWindow::activateAccepted() -{ - QMessageBox::information(this, tr("Success"), tr("Account activation accepted.\nWill now login.")); -} - // Actions void MainWindow::actConnect() { - dlgConnect = new DlgConnect(this); - connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &MainWindow::actForgotPasswordRequest); - - if (dlgConnect->exec()) { - client->connectToServer(dlgConnect->getHost(), static_cast(dlgConnect->getPort()), - dlgConnect->getPlayerName(), dlgConnect->getPassword()); - } -} - -void MainWindow::actRegister() -{ - DlgRegister dlg(this); - if (dlg.exec()) { - client->registerToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(), - dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName()); - } + connectionController->connectToServer(); } void MainWindow::actDisconnect() { - client->disconnectFromServer(); + connectionController->disconnectFromServer(); } void MainWindow::actSinglePlayer() @@ -267,13 +179,15 @@ void MainWindow::actWatchReplay() QFileDialog dlg(this, tr("Load replay")); dlg.setDirectory(SettingsCache::instance().getReplaysPath()); dlg.setNameFilters(QStringList() << QObject::tr("Cockatrice replays (*.cor)")); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } QString fileName = dlg.selectedFiles().at(0); QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { return; + } QByteArray buf = file.readAll(); file.close(); @@ -298,10 +212,11 @@ void MainWindow::localGameEnded() void MainWindow::actFullScreen(bool checked) { - if (checked) + if (checked) { setWindowState(windowState() | Qt::WindowFullScreen); - else + } else { setWindowState(windowState() & ~Qt::WindowFullScreen); + } } void MainWindow::actSettings() @@ -373,292 +288,9 @@ void MainWindow::actOpenSettingsFolder() QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } -void MainWindow::serverTimeout() -{ - QMessageBox::critical(this, tr("Error"), tr("Server timeout")); - actConnect(); -} - -void MainWindow::loginError(Response::ResponseCode r, - QString reasonStr, - quint32 endTime, - QList missingFeatures) -{ - switch (r) { - case Response::RespClientUpdateRequired: { - QString formattedMissingFeatures; - formattedMissingFeatures = "Missing Features: "; - for (int i = 0; i < missingFeatures.size(); ++i) - formattedMissingFeatures.append(QString("\n %1").arg(QChar(0x2022)) + " " + - missingFeatures.value(i)); - - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setWindowTitle(tr("Failed Login")); - msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") + - "\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'.")); - msgBox.setDetailedText(formattedMissingFeatures); - msgBox.exec(); - break; - } - case Response::RespWrongPassword: - QMessageBox::critical( - this, tr("Error"), - tr("Incorrect username or password. Please check your authentication information and try again.")); - break; - case Response::RespWouldOverwriteOldSession: - QMessageBox::critical(this, tr("Error"), - tr("There is already an active session using this user name.\nPlease close that " - "session first and re-login.")); - break; - case Response::RespUserIsBanned: { - QString bannedStr; - if (endTime) - bannedStr = tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString()); - else - bannedStr = tr("You are banned indefinitely."); - if (!reasonStr.isEmpty()) - bannedStr.append("\n\n" + reasonStr); - - QMessageBox::critical(this, tr("Error"), bannedStr); - break; - } - case Response::RespUsernameInvalid: { - QMessageBox::critical(this, tr("Error"), extractInvalidUsernameMessage(reasonStr)); - break; - } - case Response::RespRegistrationRequired: - if (QMessageBox::question(this, tr("Error"), - tr("This server requires user registration. Do you want to register now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - actRegister(); - } - break; - case Response::RespClientIdRequired: - QMessageBox::critical( - this, tr("Error"), - tr("This server requires client IDs. Your client is either failing to generate an ID or you are " - "running a modified client.\nPlease close and reopen your client to try again.")); - break; - case Response::RespContextError: - QMessageBox::critical(this, tr("Error"), - tr("An internal error has occurred, please close and reopen Cockatrice before trying " - "again.\nIf the error persists, ensure you are running the latest version of the " - "software and if needed contact the software developers.")); - break; - case Response::RespAccountNotActivated: { - bool ok = false; - QString token = getTextWithMax(this, tr("Account activation"), - tr("Your account has not been activated yet.\nYou need to provide " - "the activation token received in the activation email."), - QLineEdit::Normal, QString(), &ok); - if (ok && !token.isEmpty()) { - client->activateToServer(token); - return; - } - client->disconnectFromServer(); - break; - } - case Response::RespServerFull: { - QMessageBox::critical(this, tr("Server Full"), - tr("The server has reached its maximum user capacity, please check back later.")); - break; - } - default: - QMessageBox::critical(this, tr("Error"), - tr("Unknown login error: %1").arg(static_cast(r)) + - tr("\nThis usually means that your client version is out of date, and the server " - "sent a reply your client doesn't understand.")); - break; - } - actConnect(); -} - -QString MainWindow::extractInvalidUsernameMessage(QString &in) -{ - QString out = tr("Invalid username.") + "
"; - QStringList rules = in.split(QChar('|')); - if (rules.size() == 7 || rules.size() == 9) { - out += tr("Your username must respect these rules:") + "
    "; - - out += "
  • " + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "
  • "; - out += "
  • " + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) + - "
  • "; - out += "
  • " + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) + - "
  • "; - out += - "
  • " + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "
  • "; - - if (rules.at(6).size() > 0) - out += "
  • " + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "
  • "; - - out += "
  • " + - tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) + - "
  • "; - - if (rules.size() == 9) { - if (rules.at(7).size() > 0) { - QString words = rules.at(7).toHtmlEscaped(); - if (words.startsWith("\n")) { - out += tr("no unacceptable language as specified by these server rules:", - "note that the following lines will not be translated"); - for (QString &line : words.split("\n", Qt::SkipEmptyParts)) { - out += "
  • " + line + "
  • "; - } - } else { - out += "
  • " + tr("can not contain any of the following words: %1").arg(words) + "
  • "; - } - } - - if (rules.at(8).size() > 0) - out += "
  • " + - tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) + - "
  • "; - } - - out += "
"; - } else { - out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username."); - } - - return out; -} - -void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime) -{ - switch (r) { - case Response::RespRegistrationDisabled: - QMessageBox::critical(this, tr("Registration denied"), - tr("Registration is currently disabled on this server")); - break; - case Response::RespUserAlreadyExists: - QMessageBox::critical(this, tr("Registration denied"), - tr("There is already an existing account with the same user name.")); - break; - case Response::RespEmailRequiredToRegister: - QMessageBox::critical(this, tr("Registration denied"), - tr("It's mandatory to specify a valid email address when registering.")); - break; - case Response::RespEmailBlackListed: - if (reasonStr.isEmpty()) { - reasonStr = - "The email address provider used during registration has been blocked from use on this server."; - } - QMessageBox::critical(this, tr("Registration denied"), reasonStr); - break; - case Response::RespTooManyRequests: - QMessageBox::critical( - this, tr("Registration denied"), - tr("It appears you are attempting to register a new account on this server yet you already have an " - "account registered with the email provided. This server restricts the number of accounts a user " - "can register per address. Please contact the server operator for further assistance or to obtain " - "your credential information.")); - break; - case Response::RespPasswordTooShort: - QMessageBox::critical(this, tr("Registration denied"), tr("Password too short.")); - break; - case Response::RespUserIsBanned: { - QString bannedStr; - if (endTime) - bannedStr = tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString()); - else - bannedStr = tr("You are banned indefinitely."); - if (!reasonStr.isEmpty()) - bannedStr.append("\n\n" + reasonStr); - - QMessageBox::critical(this, tr("Error"), bannedStr); - break; - } - case Response::RespUsernameInvalid: { - QMessageBox::critical(this, tr("Error"), extractInvalidUsernameMessage(reasonStr)); - break; - } - case Response::RespRegistrationFailed: - QMessageBox::critical(this, tr("Error"), tr("Registration failed for a technical problem on the server.")); - break; - case Response::RespNotConnected: - QMessageBox::critical(this, tr("Error"), tr("The connection to the server has been lost.")); - break; - default: - QMessageBox::critical(this, tr("Error"), - tr("Unknown registration error: %1").arg(static_cast(r)) + - tr("\nThis usually means that your client version is out of date, and the server " - "sent a reply your client doesn't understand.")); - } - actRegister(); -} - -void MainWindow::activateError() -{ - QMessageBox::critical(this, tr("Error"), tr("Account activation failed")); - client->disconnectFromServer(); - actConnect(); -} - -void MainWindow::socketError(const QString &errorStr) -{ - QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr)); - actConnect(); -} - -void MainWindow::protocolVersionMismatch(int localVersion, int remoteVersion) -{ - if (localVersion > remoteVersion) - QMessageBox::critical(this, tr("Error"), - tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice " - "version or connect to a suitable server.\nLocal version is %1, remote version is %2.") - .arg(localVersion) - .arg(remoteVersion)); - else - QMessageBox::critical(this, tr("Error"), - tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\nLocal " - "version is %1, remote version is %2.") - .arg(localVersion) - .arg(remoteVersion)); -} - -void MainWindow::setClientStatusTitle() -{ - switch (client->getStatus()) { - case StatusConnecting: - setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName())); - break; - case StatusRegistering: - setWindowTitle(appName + " - " + - tr("Registering to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); - break; - case StatusDisconnected: - setWindowTitle(appName + " - " + tr("Disconnected")); - break; - case StatusLoggingIn: - setWindowTitle(appName + " - " + tr("Connected, logging in at %1").arg(client->peerName())); - break; - case StatusLoggedIn: - setWindowTitle(client->getUserName() + "@" + client->peerName()); - break; - case StatusRequestingForgotPassword: - setWindowTitle( - appName + " - " + - tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); - break; - case StatusSubmitForgotPasswordChallenge: - setWindowTitle( - appName + " - " + - tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); - break; - case StatusSubmitForgotPasswordReset: - setWindowTitle( - appName + " - " + - tr("Requesting forgotten password to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); - break; - default: - setWindowTitle(appName); - } -} - void MainWindow::retranslateUi() { - setClientStatusTitle(); + connectionController->refreshWindowTitle(); aConnect->setText(tr("&Connect...")); aDisconnect->setText(tr("&Disconnect")); @@ -717,9 +349,9 @@ void MainWindow::createActions() aFullScreen->setCheckable(true); connect(aFullScreen, &QAction::toggled, this, &MainWindow::actFullScreen); aRegister = new QAction(this); - connect(aRegister, &QAction::triggered, this, &MainWindow::actRegister); + connect(aRegister, &QAction::triggered, connectionController, &ConnectionController::registerToServer); aForgotPassword = new QAction(this); - connect(aForgotPassword, &QAction::triggered, this, &MainWindow::actForgotPasswordRequest); + connect(aForgotPassword, &QAction::triggered, connectionController, &ConnectionController::forgotPasswordRequest); aSettings = new QAction(this); connect(aSettings, &QAction::triggered, this, &MainWindow::actSettings); aExit = new QAction(this); @@ -844,38 +476,22 @@ MainWindow::MainWindow(QWidget *parent) &MainWindow::pixmapCacheSizeChanged); pixmapCacheSizeChanged(SettingsCache::instance().getPixmapCacheSize()); - client = new RemoteClient(nullptr, &SettingsCache::instance()); - connect(client, &RemoteClient::connectionClosedEventReceived, this, &MainWindow::processConnectionClosedEvent); - connect(client, &RemoteClient::serverShutdownEventReceived, this, &MainWindow::processServerShutdownEvent); - connect(client, &RemoteClient::loginError, this, &MainWindow::loginError); - connect(client, &RemoteClient::socketError, this, &MainWindow::socketError); - connect(client, &RemoteClient::serverTimeout, this, &MainWindow::serverTimeout); - connect(client, &RemoteClient::statusChanged, this, &MainWindow::statusChanged); - connect(client, &RemoteClient::protocolVersionMismatch, this, &MainWindow::protocolVersionMismatch); - connect(client, &RemoteClient::userInfoChanged, this, &MainWindow::userInfoReceived, Qt::BlockingQueuedConnection); - connect(client, &RemoteClient::notifyUserAboutUpdate, this, &MainWindow::notifyUserAboutUpdate); - connect(client, &RemoteClient::registerAccepted, this, &MainWindow::registerAccepted); - connect(client, &RemoteClient::registerAcceptedNeedsActivate, this, &MainWindow::registerAcceptedNeedsActivate); - connect(client, &RemoteClient::registerError, this, &MainWindow::registerError); - connect(client, &RemoteClient::activateAccepted, this, &MainWindow::activateAccepted); - connect(client, &RemoteClient::activateError, this, &MainWindow::activateError); - connect(client, &RemoteClient::sigForgotPasswordSuccess, this, &MainWindow::forgotPasswordSuccess); - connect(client, &RemoteClient::sigForgotPasswordError, this, &MainWindow::forgotPasswordError); - connect(client, &RemoteClient::sigPromptForForgotPasswordReset, this, &MainWindow::promptForgotPasswordReset); - connect(client, &RemoteClient::sigPromptForForgotPasswordChallenge, this, - &MainWindow::promptForgotPasswordChallenge); - - clientThread = new QThread(this); - client->moveToThread(clientThread); - clientThread->start(); + connectionController = new ConnectionController(this, this); createActions(); createMenus(); - tabSupervisor = new TabSupervisor(client, tabsMenu, this); + connect(connectionController, &ConnectionController::windowTitleChanged, this, &MainWindow::setWindowTitle); + connect(connectionController, &ConnectionController::statusChanged, this, &MainWindow::statusChanged); + + tabSupervisor = new TabSupervisor(connectionController->client(), tabsMenu, this); connect(tabSupervisor, &TabSupervisor::setMenu, this, &MainWindow::updateTabMenu); connect(tabSupervisor, &TabSupervisor::localGameEnded, this, &MainWindow::localGameEnded); connect(tabSupervisor, &TabSupervisor::showWindowIfHidden, this, &MainWindow::showWindowIfHidden); + connect(connectionController, &ConnectionController::tabSupervisorStartRequested, tabSupervisor, + &TabSupervisor::start); + connect(connectionController, &ConnectionController::tabSupervisorStopRequested, tabSupervisor, + &TabSupervisor::stop); tabSupervisor->initStartupTabs(); setCentralWidget(tabSupervisor); @@ -1043,9 +659,6 @@ MainWindow::~MainWindow() cardUpdateProcess->waitForFinished(1000); cardUpdateProcess = nullptr; } - - client->deleteLater(); - clientThread->wait(); } void MainWindow::createTrayIcon() @@ -1079,20 +692,13 @@ void MainWindow::actShow() }); } -void MainWindow::promptForgotPasswordChallenge() -{ - DlgForgotPasswordChallenge dlg(this); - if (dlg.exec()) - client->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast(dlg.getPort()), - dlg.getPlayerName(), dlg.getEmail()); -} - void MainWindow::closeEvent(QCloseEvent *event) { // workaround Qt bug where closeEvent gets called twice static bool bClosingDown = false; - if (bClosingDown) + if (bClosingDown) { return; + } bClosingDown = true; if (!tabSupervisor->close()) { @@ -1109,20 +715,21 @@ void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) + if (event->type() == QEvent::LanguageChange) { retranslateUi(); - else if (event->type() == QEvent::ActivationChange) { + } else if (event->type() == QEvent::ActivationChange) { if (isActiveWindow() && !bHasActivated) { bHasActivated = true; if (!connectTo.isEmpty()) { qCInfo(WindowMainStartupAutoconnectLog) << "Command line connect to " << connectTo; - client->connectToServer(connectTo.host(), connectTo.port(), connectTo.userName(), connectTo.password()); + connectionController->connectToServerDirect(connectTo.host(), connectTo.port(), connectTo.userName(), + connectTo.password()); } else if (SettingsCache::instance().servers().getAutoConnect() && !SettingsCache::instance().debug().getLocalGameOnStartup()) { qCInfo(WindowMainStartupAutoconnectLog) << "Attempting auto-connect..."; DlgConnect dlg(this); - client->connectToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(), - dlg.getPassword()); + connectionController->connectToServerDirect(dlg.getHost(), static_cast(dlg.getPort()), + dlg.getPlayerName(), dlg.getPassword()); } } } @@ -1173,6 +780,13 @@ void MainWindow::cardDatabaseLoadingFailed() void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames) { + if (SettingsCache::instance().getAlwaysEnableNewSets()) { + CardDatabaseManager::getInstance()->enableAllUnknownSets(); + const auto reloadOk1 = + QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); }); + return; + } + QMessageBox msgBox(this); msgBox.setWindowTitle(tr("New sets found")); msgBox.setIcon(QMessageBox::Question); @@ -1183,6 +797,7 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow .arg(unknownSetsNames.join(", "))); QPushButton *yesButton = msgBox.addButton(tr("Yes"), QMessageBox::YesRole); + QPushButton *yesAlwaysButton = msgBox.addButton(tr("Yes, always enable"), QMessageBox::YesRole); QPushButton *noButton = msgBox.addButton(tr("No"), QMessageBox::NoRole); QPushButton *settingsButton = msgBox.addButton(tr("View sets"), QMessageBox::ActionRole); msgBox.setDefaultButton(yesButton); @@ -1191,7 +806,13 @@ void MainWindow::cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknow if (msgBox.clickedButton() == yesButton) { CardDatabaseManager::getInstance()->enableAllUnknownSets(); - const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); + const auto reloadOk1 = + QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); }); + } else if (msgBox.clickedButton() == yesAlwaysButton) { + CardDatabaseManager::getInstance()->enableAllUnknownSets(); + const auto reloadOk1 = + QtConcurrent::run([] { CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); }); + SettingsCache::instance().setAlwaysEnableNewSets(true); } else if (msgBox.clickedButton() == noButton) { CardDatabaseManager::getInstance()->markAllSetsAsKnown(); } else if (msgBox.clickedButton() == settingsButton) { @@ -1381,15 +1002,6 @@ void MainWindow::refreshShortcuts() aStatusBar->setShortcuts(shortcuts.getShortcut("MainWindow/aStatusBar")); } -void MainWindow::notifyUserAboutUpdate() -{ - QMessageBox::information( - this, tr("Information"), - tr("This server supports additional features that your client doesn't have.\nThis is most likely not a " - "problem, but this message might mean there is a new version of Cockatrice available or this server is " - "running a custom or pre-release version.\n\nTo update your client, go to Help -> Check for Updates.")); -} - void MainWindow::actOpenCustomFolder() { QString dir = SettingsCache::instance().getCustomPicsPath(); @@ -1463,8 +1075,9 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir) QStringList::const_iterator filesIterator; for (filesIterator = files.constBegin(); filesIterator != files.constEnd(); ++filesIterator) { int fileIndex = (*filesIterator).split(".").at(0).toInt(); - if (fileIndex > maxIndex) + if (fileIndex > maxIndex) { maxIndex = fileIndex; + } } return maxIndex + 1; @@ -1473,7 +1086,7 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir) void MainWindow::actReloadCardDatabase() { const auto reloadOk1 = QtConcurrent::run([] { - CardDatabaseManager::getInstance()->loadCardDatabases(); + CardDatabaseManager::getInstance()->reloadCardDatabasesAndNotify(); SettingsCache::instance().downloads().sync(); }); } @@ -1490,42 +1103,3 @@ void MainWindow::actEditTokens() dlg.exec(); CardDatabaseManager::getInstance()->saveCustomTokensToFile(); } - -void MainWindow::actForgotPasswordRequest() -{ - DlgForgotPasswordRequest dlg(this); - if (dlg.exec()) - client->requestForgotPasswordToServer(dlg.getHost(), static_cast(dlg.getPort()), - dlg.getPlayerName()); -} - -void MainWindow::forgotPasswordSuccess() -{ - QMessageBox::information( - this, tr("Reset Password"), - tr("Your password has been reset successfully, you can now log in using the new credentials.")); - SettingsCache::instance().servers().setFPHostName(""); - SettingsCache::instance().servers().setFPPort(""); - SettingsCache::instance().servers().setFPPlayerName(""); -} - -void MainWindow::forgotPasswordError() -{ - QMessageBox::warning( - this, tr("Reset Password"), - tr("Failed to reset user account password, please contact the server operator to reset your password.")); - SettingsCache::instance().servers().setFPHostName(""); - SettingsCache::instance().servers().setFPPort(""); - SettingsCache::instance().servers().setFPPlayerName(""); -} - -void MainWindow::promptForgotPasswordReset() -{ - QMessageBox::information(this, tr("Reset Password"), - tr("Activation request received, please check your email for an activation token.")); - DlgForgotPasswordReset dlg(this); - if (dlg.exec()) { - client->submitForgotPasswordResetToServer(dlg.getHost(), static_cast(dlg.getPort()), - dlg.getPlayerName(), dlg.getToken(), dlg.getPassword()); - } -} diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index ed6de5b0d..5f631ddc3 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -20,11 +20,12 @@ /** * @file window_main.h * @ingroup Core - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef WINDOW_H #define WINDOW_H +#include "connection_controller/remote_connection_controller.h" #include "widgets/dialogs/dlg_local_game_options.h" #include @@ -68,38 +69,19 @@ public slots: private slots: void updateTabMenu(const QList &newMenuList); void statusChanged(ClientStatus _status); - void processConnectionClosedEvent(const Event_ConnectionClosed &event); - void processServerShutdownEvent(const Event_ServerShutdown &event); - void serverTimeout(); - void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList missingFeatures); - void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); - void activateError(); - void socketError(const QString &errorStr); - void protocolVersionMismatch(int localVersion, int remoteVersion); - void userInfoReceived(const ServerInfo_User &userInfo); - void registerAccepted(); - void registerAcceptedNeedsActivate(); - void activateAccepted(); void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); - void notifyUserAboutUpdate(); void actDisconnect(); void actSinglePlayer(); void actWatchReplay(); void actFullScreen(bool checked); - void actRegister(); void actSettings(); - void actForgotPasswordRequest(); void actAbout(); void actTips(); void actUpdate(); void actViewLog(); void actOpenSettingsFolder(); - void forgotPasswordSuccess(); - void forgotPasswordError(); - void promptForgotPasswordReset(); void actShow(); - void promptForgotPasswordChallenge(); void showWindowIfHidden(); void cardUpdateError(QProcess::ProcessError err); @@ -125,7 +107,6 @@ private slots: private: static const QString appName; static const QStringList fileNameFilters; - void setClientStatusTitle(); void retranslateUi(); void createActions(); void createMenus(); @@ -152,14 +133,11 @@ private: TabSupervisor *tabSupervisor; WndSets *wndSets; - RemoteClient *client; - QThread *clientThread; + ConnectionController *connectionController; LocalServer *localServer; bool bHasActivated, askedForDbUpdater; - QMessageBox serverShutdownMessageBox; QProcess *cardUpdateProcess; DlgViewLog *logviewDialog; - DlgConnect *dlgConnect; GameReplay *replay; DlgTipOfTheDay *tip; QUrl connectTo; @@ -180,7 +158,6 @@ public: protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; - QString extractInvalidUsernameMessage(QString &in); }; #endif diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 7092a3fd7..ad68d4be9 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -158,9 +158,11 @@ QString const generateClientID() { QString macList; for (const QNetworkInterface &networkInterface : QNetworkInterface::allInterfaces()) { - if (networkInterface.hardwareAddress() != "") - if (networkInterface.hardwareAddress() != "00:00:00:00:00:00:00:E0") + if (networkInterface.hardwareAddress() != "") { + if (networkInterface.hardwareAddress() != "00:00:00:00:00:00:00:E0") { macList += networkInterface.hardwareAddress() + "."; + } + } } QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15); return strClientID; diff --git a/cockatrice/src/main.h b/cockatrice/src/main.h index b6e086744..ef75a41f4 100644 --- a/cockatrice/src/main.h +++ b/cockatrice/src/main.h @@ -1,8 +1,8 @@ /** * @file main.h * @ingroup Core - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MAIN_H #define MAIN_H diff --git a/cockatrice/themes/CMakeLists.txt b/cockatrice/themes/CMakeLists.txt index 3e07c54dd..577977551 100644 --- a/cockatrice/themes/CMakeLists.txt +++ b/cockatrice/themes/CMakeLists.txt @@ -2,7 +2,7 @@ # # add themes subfolders -set(defthemes Fabric Leather Plasma VelvetMarble) +set(defthemes Default Fabric Fusion Leather Plasma VelvetMarble) if(UNIX) if(APPLE) diff --git a/cockatrice/themes/Default/palette-default-dark.toml b/cockatrice/themes/Default/palette-default-dark.toml new file mode 100644 index 000000000..3ee174a2f --- /dev/null +++ b/cockatrice/themes/Default/palette-default-dark.toml @@ -0,0 +1,63 @@ +[Palette] +WindowText = #ffffffff +Button = #ff383838 +Light = #ff737373 +Midlight = #ff525252 +Dark = #ff161616 +Mid = #ff252525 +Text = #ffffffff +BrightText = #ffb4dd8b +ButtonText = #ffffffff +Base = #ff2b2b2b +Window = #ff1c1c1c +Shadow = #ff000000 +HighlightedText = #ff000000 +Link = #ffcde4b6 +LinkVisited = #ff99d999 +AlternateBase = #ff242424 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #6effffff + +[Palette.Disabled] +WindowText = #ff9d9d9d +Button = #ff1c1c1c +Light = #ff737373 +Midlight = #ff525252 +Dark = #ff161616 +Mid = #ff252525 +Text = #ff9d9d9d +BrightText = #ffb4dd8b +ButtonText = #ff787878 +Base = #ff1c1c1c +Window = #ff1c1c1c +Shadow = #ff000000 +HighlightedText = #ff9d9d9d +Link = #ff308cc6 +LinkVisited = #ffb450ff +AlternateBase = #ff242424 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #46ffffff + +[Palette.Inactive] +WindowText = #ffffffff +Button = #ff383838 +Light = #ff737373 +Midlight = #ff525252 +Dark = #ff161616 +Mid = #ff252525 +Text = #ffffffff +BrightText = #ffb4dd8b +ButtonText = #ffffffff +Base = #ff2b2b2b +Window = #ff1c1c1c +Shadow = #ff000000 +HighlightedText = #ffffffff +Link = #ffcde4b6 +LinkVisited = #ff99d999 +AlternateBase = #ff242424 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #6effffff + diff --git a/cockatrice/themes/Default/theme.cfg b/cockatrice/themes/Default/theme.cfg new file mode 100644 index 000000000..d2016a238 --- /dev/null +++ b/cockatrice/themes/Default/theme.cfg @@ -0,0 +1,5 @@ +[Appearance] +ColorScheme = Light + +[Style] +Name = Default diff --git a/cockatrice/themes/Fusion/palette-default-dark.toml b/cockatrice/themes/Fusion/palette-default-dark.toml new file mode 100644 index 000000000..c1d83a4cd --- /dev/null +++ b/cockatrice/themes/Fusion/palette-default-dark.toml @@ -0,0 +1,69 @@ +[Palette] +WindowText = #ffffffff +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ffffffff +BrightText = #ff00f652 +ButtonText = #ffffffff +Base = #ff2d2d2d +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff00f652 +LinkVisited = #ff00d346 +AlternateBase = #ff353535 +ToolTipBase = #ff3c3c3c +ToolTipText = #ffd4d4d4 +PlaceholderText = #80ffffff +Accent = #ff00d346 + +[Palette.Disabled] +WindowText = #ff9d9d9d +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ff9d9d9d +BrightText = #ff00f652 +ButtonText = #ff9d9d9d +Base = #ff1e1e1e +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff308cc6 +LinkVisited = #ffff00ff +AlternateBase = #ff353535 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80ffffff +Accent = #ff9d9d9d + +[Palette.Inactive] +WindowText = #ffffffff +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ffffffff +BrightText = #ff00f652 +ButtonText = #ffffffff +Base = #ff2d2d2d +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff1e1e1e +HighlightedText = #ffffffff +Link = #ff00f652 +LinkVisited = #ff00d346 +AlternateBase = #ff353535 +ToolTipBase = #ff3c3c3c +ToolTipText = #ffd4d4d4 +PlaceholderText = #80ffffff +Accent = #ff1e1e1e + diff --git a/cockatrice/themes/Fusion/palette-default-light.toml b/cockatrice/themes/Fusion/palette-default-light.toml new file mode 100644 index 000000000..86c41be78 --- /dev/null +++ b/cockatrice/themes/Fusion/palette-default-light.toml @@ -0,0 +1,69 @@ +[Palette] +WindowText = #ff000000 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #ffe3e3e3 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff000000 +BrightText = #ffffffff +ButtonText = #ff000000 +Base = #ffffffff +Window = #fff0f0f0 +Shadow = #ff696969 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #ff107532 + +[Palette.Disabled] +WindowText = #ff787878 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #fff7f7f7 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff787878 +BrightText = #ffffffff +ButtonText = #ff787878 +Base = #fff0f0f0 +Window = #fff0f0f0 +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff0000ff +LinkVisited = #ffff00ff +AlternateBase = #fff7f7f7 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #ff787878 + +[Palette.Inactive] +WindowText = #ff000000 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #ffe3e3e3 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff000000 +BrightText = #ffffffff +ButtonText = #ff000000 +Base = #ffffffff +Window = #fff0f0f0 +Shadow = #ff696969 +Highlight = #fff0f0f0 +HighlightedText = #ff000000 +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #fff0f0f0 + diff --git a/cockatrice/themes/Fusion/theme.cfg b/cockatrice/themes/Fusion/theme.cfg new file mode 100644 index 000000000..9b38e505e --- /dev/null +++ b/cockatrice/themes/Fusion/theme.cfg @@ -0,0 +1,5 @@ +[Appearance] +ColorScheme = Dark + +[Style] +Name = Fusion diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index c13bfb2df..c210315c7 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Chyba - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Nastavení vzhledu - + Current theme: Aktuální vzhled - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Vykreslování karet - + Display card names on cards having a picture Zobrazit jména karet na kartách s obrázky - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Zvětšovat karty při najetí myši - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Rozvržení ruky - + Display hand horizontally (wastes space) Zobrazit ruku horizontálně (plýtvá místem) - + Enable left justification Zapnout - + Table grid layout Rozložení herní mřížky - + Invert vertical coordinate Převrátit vertikální souřadnice - + Minimum player count for multi-column layout: Minimální počet hráčů pro víceřádkové rozvržení: - + Maximum font size for information displayed on cards: Maximální velikost písma pro informace na kartách @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorSettingsPage - - + + Update Spoilers Aktualizovat spoilery - - + + Success Úspěch - + Download URLs have been reset. - + Downloaded card pictures have been reset. Stáhnuté obrázky karet byly resetovány. - + Error Chyba - + One or more downloaded card pictures could not be cleared. Některé stáhnuté obrázky karet nemohli být vymazány. - + Add URL - - + + URL: URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Aktualizuji... - + Choose path Vyberte cestu - + URL Download Priority - + Spoilers Spoilery - + Download Spoilers Automatically Stahovat spoilery automaticky - + Spoiler Location: - + Last Change Poslední změna - + Spoilers download automatically on launch Automaticky stahovat spoilery při spuštění - + Press the button to manually update without relaunching Manuálně aktualizovat bez restartování - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images Smazat stáhnuté obrázky - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckListModel - + Count - + Set - + Number Počet - + Provider ID - + Card Karta @@ -1545,12 +1528,12 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - - + + Error Chyba - + The selected file could not be loaded. Vybraný soubor se nepodařilo načíst. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2885,17 +2868,17 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgLoadDeckFromClipboard - + Load deck from clipboard Nahrát balíček ze schránky - + Error Chyba - + Invalid deck list. Neplatný formát balíčku. @@ -2903,43 +2886,43 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Nahrát balíček + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3152,12 +3168,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Neznámý error při nahrávání databáze karet. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3174,7 +3190,7 @@ Je možné že bude nutné znovu spustit Oracle pro obnovení databáze karet. Chtěl/a by jste změnit nastavení lokace databáze? - + Your card database version is too old. This can cause problems loading card information or images @@ -3191,7 +3207,7 @@ Je možné to spravit spuštěním Oracle pro aktualizaci vaší databáze karet Chcete změnit nastavení lokace databáze? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3200,7 +3216,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3209,7 +3225,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3218,7 +3234,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3227,59 +3243,59 @@ Would you like to change your database location setting? - - - + + + Error Chyba - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s balíčky je neplatná. Chcete se vrátit a nastavit správnou? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s obrázky je neplatná. Chcete se vrátit a nastavit správnou? - + Settings Nastavení - + General Obecné - + Appearance Vzhled - + User Interface Rozhraní - + Card Sources - + Chat Chat - + Sound Zvuk - + Shortcuts Skratky @@ -3592,67 +3608,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4100,143 +4116,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Vyberte cestu - + Personal settings Osobní nastavení - + Language: Jazyk: - + Paths (editing disabled in portable mode) - + Paths Cesty - + How to help with translations - + Decks directory: Adresář s balíčky: - + Filters directory: - + Replays directory: Adresář se záznamy her - + Pictures directory: Adresář s obrázky: - + Card database: Databáze karet: - + Custom database directory: - + Token database: Databáze tokenů: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automaticky spustit Oracle při spouštění nové verze Cockatrice - + Show tips on startup Ukázat tip při spuštění - + Last update check on %1 (%2 days ago) @@ -4244,47 +4260,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4292,88 +4308,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4381,52 +4397,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4434,193 +4450,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4712,18 +4748,8 @@ Will now login. - - Number of players - Počet hráčů - - - - Please enter the number of players. - Vložte počet hráčů. - - - - + + Player %1 Hráč %1 @@ -4826,8 +4852,8 @@ Will now login. - - + + Error Chyba @@ -5231,144 +5257,144 @@ Lokální verze je %1, verze serveru je %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5376,54 +5402,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5474,7 +5500,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5628,590 +5654,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile z exilnutých karet - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard ze sideboardu - + from the stack ze stacku - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card kartu - + %1 gives %2 control over %3. %1 předává kontrolu hráči %2 karty %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 dává kartu %2 %3 do hry. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 exiluje %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 přesouvá %2%3 do sideboardu. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 sesílá %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Hra byla ukončena. - + The game has started. Hra začíná. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 nyní sleduje hru. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6219,110 +6265,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Přidat zprávu - - + + Message: Zpráva: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6335,32 +6381,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6502,57 +6553,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Odtapovací fáze - + Upkeep step Upkeep - + Draw step Lízací fáze - + First main phase První hlavní fáze - + Beginning of combat step Začátek bojové fáze - + Declare attackers step Oznámení útočníků - + Declare blockers step Oznámení blokujících - + Combat damage step Udělení bojového zranění - + End of combat step Konec bojové fáze - + Second main phase Druhá hlavní fáze - + End of turn step Konec kola @@ -6569,134 +6620,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6704,48 +6759,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6794,17 +6866,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6943,6 +7023,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7091,37 +7198,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7164,6 +7271,14 @@ Cockatrice will now reload the card database. Hry + + SayMenu + + + S&ay + + + SequenceEdit @@ -7228,53 +7343,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7341,27 +7456,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7577,59 +7692,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7637,60 +7816,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 @@ -7698,61 +7869,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7817,7 +7982,7 @@ Please check your shortcut settings! - + New folder Nová složka @@ -7899,18 +8064,18 @@ Prosím vložte jméno: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Název nové složky: @@ -7928,12 +8093,12 @@ Prosím vložte jméno: - + Error - + Could not open deck at %1 @@ -7982,197 +8147,191 @@ Prosím vložte jméno: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Fáze - + &Game &Hra - + Next &phase Další &fáze - + Next phase with &action - + Next &turn Další &kolo - + Reverse turn order - + &Remove all local arrows &Odstranit všechny lokální šipky - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede &Ukončit hru - + &Leave game &Opustit hru - + C&lose replay - + &Focus Chat - + &Say: &Chat: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede Ukončit hru - + Are you sure you want to concede this game? Opravdu chcete ukončit tuto hru? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Opustit hru - + Are you sure you want to leave this game? Opravdu chcete opustit tuto hru? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9266,142 +9425,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Obecné - + &Double-click cards to play them (instead of single-click) &Pro zahraní karty je třeba dvojklik - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Animace - - - - &Tap/untap animation - &Animace tapnutí/odtapnutí - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animace + + + + &Tap/untap animation + &Animace tapnutí/odtapnutí - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9465,23 +9634,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9508,25 +9678,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9534,22 +9787,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9585,7 +9848,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9593,19 +9856,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9613,27 +9876,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9647,52 +9920,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9700,56 +9943,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9757,17 +10008,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9783,47 +10034,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9924,133 +10180,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10058,72 +10314,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing zamíchat po zavření - + pile view @@ -10158,7 +10414,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10239,7 +10495,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10391,7 +10647,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10812,7 +11068,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10832,98 +11089,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile Exil - + - + Graveyard Hřbitov - + Hand Ruka - - + + Top of Library - - + Battlefield, Face Down @@ -10959,234 +11220,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Vzdát hru - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index ed1a88be8..564f39e25 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &aktualisieren - + Parse Set Name and Number (if available) Setnamen und -Nummer parsen (falls verfügbar) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab In neuem Reiter öffnen - + Are you sure? Sind Sie sicher? - + The decklist has been modified. Do you want to save the changes? Die Deckliste wurde bearbeitet. Änderungen speichern? - - - - - - + + + + + + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden - + Could not save remote deck Remote-Deck konnte nicht gespeichert werden - - + + The deck could not be saved. Please check that the directory is writable and try again. Das Deck konnte nicht abgespeichert werden. Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen Sie es erneut. - + Save deck Deck speichern - + The deck could not be saved. Das Deck konnte nicht abgespeichert werden. - + There are no cards in your deck to be exported Es gibt keine Karten in Ihrem Deck zum Exportieren @@ -133,202 +133,168 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen AppearanceSettingsPage - + seconds Sekunden - + Error Fehler - + Could not create themes directory at '%1'. Das Themeverzeichnis '%1' konnte nicht erstellt werden. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Diese Funktion aktivieren wird die Nutzung der Druckauswahl deaktivieren. - -Es wird ihnen nicht möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis zu verwalten, oder die Drucke, die andere Personen für ihre Decks ausgewählt haben, zu sehen. - -Sie werden den Set-Manager verwenden müssen, der über Kartendatenbank -> Sets verwalten verfügbar ist. - -Sind sie sicher, dass diese Funktion aktiviert werden soll? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Diese Funktion deaktivieren wird die Nutzung der Druckauswahl aktivieren. - -Es wird ihnen nun möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis im Deck-Editor zu verwalten und zu konfigurieren, welcher Druck standardmäßig in ein Deck hinzugefügt wird, indem sie es anheften in der Druckauswahl - -Sie können auch den Set-Manager verwenden, um selbstständig die Sortierungsreihenfolge für Drucke in der Druckauswahl festzulegen (andere Sortierungsoptionen wie alphabetisch oder Veröffentlichkeitsdatum sind verfügbar). - -Sind sie sicher, dass diese Funktion deaktiviert werden soll? - - - - Confirm Change - Änderung bestätigen - - - + Theme settings Darstellungseinstellungen - + Current theme: Aktuelles Theme: - + Open themes folder Öffne Themeverzeichnis - + Home tab background source: Startseitenhintergrund-Quelle: - + Home tab background shuffle frequency: Startseitenhintergrund Wechselfrequenz: - + Disabled Deaktiviert - + + Display card name of background in bottom right: + Kartenname des Hintergrunds unten rechts anzeigen: + + + Menu settings Einstellungsmenü - + Show keyboard shortcuts in right-click menus Tastatur-Kürzel anzeigen bei Rechtsklickmenü - + Show game filter toolbar above list in room tab Spiel-Filter Symbolleiste über der Liste im Raum-Tab anzeigen - + Card rendering Kartendarstellung - + Display card names on cards having a picture Kartennamen auch bei Karten mit Bildern darstellen - + Auto-Rotate cards with sideways layout Automatisches Drehen von Karten mit Seitwärts-Ausrichtung - + Override all card art with personal set preference (Pre-ProviderID change behavior) Überschreiben aller Kartenbilder mit persönlichen Set-Präferenzen (Vor-AnbieterID Wechselverhalten) - + Bump sets that the deck contains cards from to the top in the printing selector Priorisieren von Sets, in welchen Karten des ausgewählten Decks vorhanden sind, nach oben in der Druckauswahl - + Scale cards on mouse over Karten beim Darüberfahren mit der Maus vergrößern - + Use rounded card corners Abgerundete Kartenecken verwenden - + Minimum overlap percentage of cards on the stack and in vertical hand Minimaler Überschneidungsanteil der Karten auf dem Stapel und in der vertikalen Hand - + Maximum initial height for card view window: Maximale Starthöhe für das Kartenansichtsfenster - - + + rows Reihen - + Maximum expanded height for card view window: Maximale ausgeklappte Höhe für das Kartenansichtsfenster: - + Card counters Kartenzähler - + Counter %1 Zähler %1 - + Hand layout Handdarstellung - + Display hand horizontally (wastes space) Hand horizontal anzeigen (verschwendet Platz) - + Enable left justification Linksbündige Ausrichtung aktivieren - + Table grid layout Spielfeldraster - + Invert vertical coordinate Vertikale Koordinate umkehren - + Minimum player count for multi-column layout: Mindestspieleranzahl für mehrspaltige Anordnung: - + Maximum font size for information displayed on cards: Maximale Schriftgröße für die Anzeige von Informationen auf Karten: @@ -336,7 +302,12 @@ Sind sie sicher, dass diese Funktion deaktiviert werden soll? ArchidektApiResponseDeckDisplayWidget - + + Back to results + Zurück zu den Ergebnissen + + + Open Deck in Deck Editor Deck im Deck-Editor öffnen @@ -656,22 +627,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoPictureWidget - + View related cards Zugehörige Karten ansehen - + Add card to deck Karte zum Deck hinzufügen - + Mainboard Mainboard - + Sideboard Sideboard @@ -707,124 +678,124 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardMenu - + Re&veal to... Zeigen an.... - + &All players &Alle Spieler - + View related cards Verwandte Karten ansehen - + Token: Spielstein: - + All tokens Alle Spielsteine - + &Select All &Alles auswählen - + S&elect Row Reihe auswählen - + S&elect Column Spalte auswählen - + &Play &Spielen - + &Hide &Verstecken - + Play &Face Down Verdeckt ausspielen - + &Tap / Untap Turn sideways or back again &Tappen/ Enttappen - - Toggle &normal untapping - Normales Enttapen umschalten + + Skip &untapping + Enttappen überspringen - + T&urn Over Turn face up/face down Umdrehen - + &Peek at card face %Kartenvorderseite ansehen - + &Clone &Kopieren - + Attac&h to card... An Karte anhängen - + Unattac&h Lösen - + &Draw arrow... &Pfeil zeichnen - + &Set annotation... &Anmerkung setzen - + Ca&rd counters Kartenzähler - + &Add counter (%1) &Zähler hinzufügen (%1) - + &Remove counter (%1) &Zähler entfernen (%1) - + &Set counters (%1)... &Zähler setzen (%1)... @@ -840,133 +811,133 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardZoneLogic - + their hand nominative ihrer Hand - + %1's hand nominative %1's Hand - + their library look at zone ihrer Bibliothek - + %1's library look at zone %1's Bibliothek - + of their library top cards of zone, von ihrer Bibliothek - + of %1's library top cards of zone von %1's Bibliothek - + their library reveal zone ihrer Bibliothek - + %1's library reveal zone %1's Bibliothek - + their library shuffle ihrer Bibliothek - + %1's library shuffle %1's Bibliothek - - - their library - nominative - ihrer Bibliothek - - - - %1's library - nominative - %1's Bibliothek - + their library + nominative + ihrer Bibliothek + + + + %1's library + nominative + %1's Bibliothek + + + their graveyard nominative ihres Friedhofs - + %1's graveyard nominative %1's Friedhof - + their exile nominative ihres Exils - + %1's exile nominative %1's Exil - - - their sideboard - look at zone - ihres Nebendecks - - - - %1's sideboard - look at zone - %1's Nebendeck - their sideboard - nominative + look at zone ihres Nebendecks %1's sideboard + look at zone + %1's Nebendeck + + + + their sideboard + nominative + ihres Nebendecks + + + + %1's sideboard nominative %1's Nebendeck - + their custom zone '%1' nominative ihrer individuellen Zone '%1' - + %1's custom zone '%2' nominative %1's individuelle Zone '%2' @@ -1028,7 +999,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorCardDatabaseDockWidget - + Card Database Kartendatenbank @@ -1036,7 +1007,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorCardInfoDockWidget - + Card Info Karteninformation @@ -1059,32 +1030,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Zum Sideboard hinzufügen - + Select Printing Auswahl der Auflage - + Show on EDHRec (Commander) Auf EDHRec ansehen (Als Commander) - + Show on EDHRec (Card) Auf EDHRec ansehen (Als Karte) - + Show Related cards Zeige ähnliche Karten - + Add card to &maindeck Karte zum &Hauptdeck hinzufügen - + Add card to &sideboard Karte zum &Sideboard hinzufügen @@ -1092,32 +1063,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorDeckDockWidget - + Loading Database... Datenbank wird geladen... - + Banner Card Bannerkarte - + Main Type Haupttyp - + Mana Cost Manakosten - + Colors Farben - + Select Printing Auswahl der Auflage @@ -1190,17 +1161,17 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorFilterDockWidget - + Filters Filter - + &Clear all filters &Alle Filter entfernen - + Delete selected Auswahl löschen @@ -1323,7 +1294,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorPrintingSelectorDockWidget - + Printing Selector Auflagenauswahl @@ -1331,166 +1302,166 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorSettingsPage - - + + Update Spoilers Spoiler aktualisieren - - + + Success Erfolgreich - + Download URLs have been reset. Download-URLs wurden zurückgesetzt. - + Downloaded card pictures have been reset. Heruntergeladene Kartenbilder wurden zurückgesetzt. - + Error Fehler - + One or more downloaded card pictures could not be cleared. Eines oder mehrere Kartenbilder konnten nicht gelöscht werden. - + Add URL URL hinzufügen - - + + URL: URL - - + + Edit URL URL bearbeiten - + Network Cache Size: Größe des Netzwerkzwischenspeichers: - + Redirect Cache TTL: Umleitungszwischenspeicher TTL: - + How long cached redirects for urls are valid for. Wie lange gespeicherte Umleitungen für URLS gültig sind. - + Picture Cache Size: Größe des Bilderzwischenspeichers: - + Add New URL Neue URL hinzufügen - + Remove URL URL entfernen - + Day(s) Tag(e) - + Updating... Aktualisiere... - + Choose path Pfad auswählen - + URL Download Priority URL Downloadpriorität - + Spoilers Spoiler - + Download Spoilers Automatically Lade Spoiler automatisch herunter - + Spoiler Location: Spoiler Verzeichnis: - + Last Change Letzte Änderung - + Spoilers download automatically on launch Spoiler werden beim Start automatisch heruntergeladen - + Press the button to manually update without relaunching Drücke den Knopf um manuell zu aktualisieren ohne neu zu starten - + Do not close settings until manual update is complete Schließen Sie die Einstellungen nicht solange die manuelle Aktualisierung nicht abgeschlossen ist. - + Download card pictures on the fly Kartenbilder dynamisch herunterladen - + How to add a custom URL Wie eine benutzerdefinierte URL hinzugefügt wird - + Delete Downloaded Images Heruntergeladene Bilder löschen - + Reset Download URLs Download-URLs zurücksetzen - + On-disk cache for downloaded pictures Festplatten-Cache für heruntergeladene Bilder - + In-memory cache for pictures not currently on screen Arbeitsspeicher-Cache für Bilder die gerade nicht angezeigt werden @@ -1498,32 +1469,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckListHistoryManagerWidget - + Undo Rückgängig machen - + Redo Wiederholen - + Undo/Redo history Rückgängig machen/Wiederholen Verlauf - + Click on an entry to revert to that point in the history. Einen Eintrag anklicken um zu diesem Zeitpunkt im Verlauf zurückzukehren - + [redo] [wiederholen]  - + [undo] [rückgängig machen]  @@ -1531,27 +1502,27 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckListModel - + Count Anzahl - + Set Festlegen - + Number Nummer - + Provider ID ID des Anbieters - + Card Karte @@ -1559,12 +1530,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckLoader - + Common deck formats (%1) Standarddeckformate (%1) - + All files (*.*) Alle Dateien (*.*) @@ -1661,94 +1632,94 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckPreviewWidget - + Banner Card Bannerkarte - + Open in deck editor Im Deckeditor öffnen - + Edit Tags Tags bearbeiten - + Rename Deck Deck umbenennen - + Save Deck to Clipboard Deck in die Zwischenablage speichern - + Annotated Kommentiert - + Annotated (No set info) Komentiert (keine Setinformationen) - + Not Annotated Nicht kommentiert - + Not Annotated (No set info) Nicht kommentiert (keine Setinformationen) - + Rename File Datei umbenennen - + Delete File Datei löschen - + Set Banner Card Bannerkarte setzen - - + + New name: Neuer Name: - - + + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - + Delete file Datei löschen - + Are you sure you want to delete the selected file? Sind Sie sicher, dass die ausgewählte Datei gelöscht werden soll? - + Delete failed Löschen fehlgeschlagen @@ -1781,32 +1752,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Format setzen zu %1 - + Added (%1): %2 (%3) %4 (%1): %2 (%3) %4 hinzugefügt - + Moved to %1 1 × "%2" (%3) Nach %1 1 × "%2" (%3) bewegt - + Removed "%1" (all copies) "%1" entfernt (alle Kopien) - + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Hinzugefügt - + Removed Entfernt @@ -1873,30 +1844,30 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Sideboard gesperrt - - + + Error Fehler - + The selected file could not be loaded. Die gewählte Datei konnte nicht geladen werden. - + Deck is greater than maximum file size. Deck übersteigt die maximale Dateigröße. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sind Sie sicher, dass sie den Start erzwingen wollen? Dies wirft alle nicht bereiten Spieler aus dem Spiel. - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditDeckInClipboard - + Edit deck in clipboard Deck in Zwischenablage bearbeiten - + Error Fehler - + Invalid deck list. Ungültige Deckliste. @@ -2902,17 +2873,17 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgLoadDeckFromClipboard - + Load deck from clipboard Deck aus der Zwischenablage laden - + Error Fehler - + Invalid deck list. Ungültige Deckliste. @@ -2920,45 +2891,45 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Hier einen Link einer Decklistenseite einfügen um ihn zu importieren. (Archidekt, Deckstats, Moxfield und TappedOut sind unterstützt.) - - - - - + + + + + Load Deck from Website Deck von Internetseite laden - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Kein Parset verfügbar für diesen Deckanbieter. (Archidekt, Deckstats, Moxfield und TappedOut sind unterstützt.) - + Network error: %1 Netzwerkfehler: %1 - + Received empty deck data. Leere Deckdaten erhalten. - + Failed to parse deck data: %1 Fehler beim parsen der Deckdaten: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2983,40 +2954,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Deck laden + + DlgLocalGameOptions + + + Players: + Spieler: + + + + General + Allgemein + + + + Starting life total: + Lebenspunkte zum Spielstart: + + + + Game setup options + Spieleinstellungen + + + + Remember settings + Einstellungen merken + + + + Local game options + Lokale Spieleinstellungen + + DlgMoveTopCardsUntil - + Card name (or search expressions): Kartenname (oder Suchbezeichnung) - + Number of hits: Anzahl der Treffer: - + Auto play hits Automatisches Ausspielen von Treffern - + Put top cards on stack until... Oberste Karten auf den Stapel legen bis... - + No cards matching the search expression exists in the card database. Proceed anyways? Keine Karten, die den Suchbezeichnungen entsprechen, existieren in der Kartendatenbank. Trotzdem fortfahren? - + Cockatrice Basilisk - + Invalid filter Ungültiger Filter @@ -3178,12 +3182,12 @@ Die E-Mail-Adresse wird zur Accountverifikation genutzt. DlgSettings - + Unknown Error loading card database Unbekannter Fehler beim Laden der Kartendatenbank - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3200,7 +3204,7 @@ Sie müssen Oracle unter Umständen nochmals ausführen um Ihre Kartendatenbank Möchten Sie Ihren Speicherort der Datenbank aktualisieren?? - + Your card database version is too old. This can cause problems loading card information or images @@ -3217,7 +3221,7 @@ Normalerweise kann dies durch einen erneuten Start von Oracle, um die Kartendate Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3230,7 +3234,7 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - + File Error loading your card database. Would you like to change your database location setting? @@ -3239,7 +3243,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3248,7 +3252,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3261,59 +3265,59 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - - - + + + Error Fehler - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Deckordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Kartenbilderordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + Settings Einstellungen - + General Allgemeines - + Appearance Erscheinungsbild - + User Interface Benutzeroberfläche - + Card Sources Kartenquellen - + Chat Chat - + Sound Töne - + Shortcuts Tastaturkürzel @@ -3630,67 +3634,67 @@ Eventuell müssen sie manuell eine neue Version herunterladen. DrawProbabilityWidget - + Draw Probability Zieh-Wahrscheinlichkeit - + Probability of drawing Wahrscheinlichkeit des Ziehens - + Card Name Kartenname - + Type Typ - + Subtype Untertyp - + Mana Value Manawert - + At least mindestens - + Exactly genau - + card(s) having drawn at least Karte(n) gezogen haben mindestens - + cards Karten - + Category Kategorie - + Qty Menge - + Odds (%) Wahrscheinlichkeit (%) @@ -4138,143 +4142,143 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GeneralSettingsPage - + Reset all paths Alle Pfade zurücksetzen - + All paths have been reset Alle Pfade wurden zurückgesetzt - - - - - - - + + + + + + + Choose path Pfad auswählen - + Personal settings Persönliche Einstellungen - + Language: Sprache: - + Paths (editing disabled in portable mode) Pfade (Anpassungen im portablen Modus deaktiviert) - + Paths Pfade - + How to help with translations Wie man beim Übersetzen helfen kann - + Decks directory: Verzeichnis mit Decklisten: - + Filters directory: Filterverzeichnis: - + Replays directory: Verzeichnis mit aufgezeichneten Spielen: - + Pictures directory: Verzeichnis mit Bilddateien: - + Card database: Kartendatenbank: - + Custom database directory: Verzeichnis für benutzerdefinierte Datenbank: - + Token database: Spielsteindatenbank: - + Update channel Aktualisierungskanal - + Check for client updates on startup Nach Aktualisierungen des Klienten beim Start der Anwendung suchen - + Check for card database updates on startup Auf Kartendatenbankaktualisierungen beim Anwendungsstart prüfen - + Don't check Nicht überprüfen - + Prompt for update Zu Aktualisierungen auffordern - + Always update in the background Immer im Hintergrund aktualisieren - + Check for card database updates every Auf Kartendatenbankaktualisierung prüfen alle - + days Tage - + Notify if a feature supported by the server is missing in my client Benachrichtigung wenn ein vom Server unterstütze Funktion in meinem Client fehlt - + Automatically run Oracle when running a new version of Cockatrice Starte Oracle automatisch, wenn eine neue Version von Cockatrice gestartet wird - + Show tips on startup Zeige Tipps beim Start - + Last update check on %1 (%2 days ago) Letzte Aktualisierungsprüfung am %1 (vor %2 Tagen) @@ -4282,47 +4286,47 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GraveyardMenu - + &Graveyard &Friedhof - + &View graveyard &Friedhof ansehen - + &Move graveyard to... & Friedhof nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &All players &Alle Spieler - + &Hand &Hand - + &Exile &Exil - + Reveal random card to... Zufällige Karte vorzeigen für ... @@ -4330,88 +4334,88 @@ Eventuell müssen sie manuell eine neue Version herunterladen. HandMenu - + &Hand &Hand - + &View hand &Hand ansehen - + Sort hand by... Hand nach ... sortieren - + Name Name - + Type Typ - + Mana Value Manawert - + Take &mulligan (Choose hand size) &Mulligan nehmen (Handgröße wählen) - + Take mulligan (Same hand size) Mulligan nehmen (Gleiche Handgröße) - + Take mulligan (Hand size - 1) Mulligan nehmen (Handgröße -1) - + &Move hand to... &Hand nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &Graveyard &Friedhof - + &Exile &Exil - + &Reveal hand to... &Hand an ... vorzeigen - - + + All players Alle Spieler - + Reveal r&andom card to... Zufällige Karten an ... vorzeigen @@ -4419,52 +4423,52 @@ Eventuell müssen sie manuell eine neue Version herunterladen. HomeWidget - + Create New Deck Neues Deck erstellen - + Browse Decks Decks durchsuchen - + Browse Card Database Kartendatenbank durchsuchen - + Browse EDHRec EDHRec durchsuchen - + Browse Archidekt Archidekt durchsuchen - + View Replays Wiederholung ansehen - + Quit Verlassen - + Connecting... Verbinden... - + Connect Verbinden - + Play Spielen @@ -4472,193 +4476,213 @@ Eventuell müssen sie manuell eine neue Version herunterladen. LibraryMenu - + &Library &Bibliothek - + &View library &Bibliothek ansehen - + View &top cards of library... Die obersten Karten der Bibliothek ansehen - + View bottom cards of library... Die untersten Karten der Bibliothek ansehen - + Reveal &library to... Bibliothek an ... vorzeigen - + Lend library to... Bibliothek ... leihen - + Reveal &top cards to... Die obersten Karten ... zeigen - + &Top of library... &Oben auf die Bibliothek - + &Bottom of library... &Unter die Bibliothek... - + &Always reveal top card &Immer die oberste Karte vorzeigen - + &Always look at top card &Immer die oberste Karte ansehen - + &Open deck in deck editor &Deck in Deckeditor öffnen - + &Draw card &Karte ziehen - + D&raw cards... Karten ziehen... - + &Undo last draw &Letztes Ziehen rückgängig machen - + Shuffle Mischen - + &Play top card &Oberste Karte spielen - + Play top card &face down Oberste Karte verdeckt ausspielen - + Put top card on &bottom Oberste Karte nach unten legen - + Move top card to grave&yard Oberste Karte in den Friedhof bewegen - + Move top card to e&xile Oberste Karte ins Exil bewegen - + Move top cards to &graveyard... Die obersten Karten in den Friedhof bewegen... - + + Move top cards to graveyard face down... + Die obersten Karten verdeckt in den Friedhof bewegen... + + + Move top cards to &exile... Die obersten Karten ins Exil bewegen - + + Move top cards to exile face down... + Die obersten Karten verdeckt ins Exil bewegen... + + + Put top cards on stack &until... Die obersten Karten auf den Stapel packen bis... - + Shuffle top cards... Oberste Karten mischeln... - + &Draw bottom card &Unterste Karte ziehen - + D&raw bottom cards... Untere Karten ziehen... - + &Play bottom card &Unterste Karte ausspielen - + Play bottom card &face down Unterste Karte verdeckt spielen - + Move bottom card to grave&yard Unterste Karte in den Friedhof bewegen - + Move bottom card to e&xile Unterste Karte ins Exil bewegen - + Move bottom cards to &graveyard... Unterste Karten in den Friedhof bewegen... - + + Move bottom cards to graveyard face down... + Die untersten Karten verdeckt in den Friedhof bewegen... + + + Move bottom cards to &exile... Unterste Karten ins Exil bewegen... - + + Move bottom cards to exile face down... + Die untersten Karten verdeckt ins Exil bewegen... + + + Put bottom card on &top Unterste Karte nach oben bewegen - + Shuffle bottom cards... Unterste Karten mischen... - - + + &All players &Alle Spieler - + Reveal top cards of library Oberste Karten der Bibliothek vorzeigen - + Number of cards: (max. %1) Anzahl der Karten: (max. %1) @@ -4756,18 +4780,8 @@ Will now login. Login läuft. - - Number of players - Spieleranzahl - - - - Please enter the number of players. - Bitte die Spieleranzahl eingeben: - - - - + + Player %1 Spieler %1 @@ -4870,8 +4884,8 @@ Login läuft. - - + + Error Fehler @@ -5279,36 +5293,36 @@ Lokale Version ist %1, Serverversion ist %2. Ein-/Ausblenden - + New Version Neue Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Herzlichen Glückwunsch zur Aktualisierung auf Cockatrice %1! Oracle wird gestartet um ihre Kartendatenbank zu aktualisieren. - + Cockatrice installed Cockatrice wurde installiert - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Herzlichen Glückwunsch, dass Sie Cockatrice %1 installiert haben! Oracle wird jetzt gestartet und installiert die initiale Kartendatenbank. - + Card database Kartendatenbank - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5317,29 +5331,29 @@ Möchten Sie Ihre Kartendatenbank jetzt aktualisieren? Falls Sie unsicher sind oder Cockatrice das erste Mal nutzen, wählen Sie „Ja“ - - + + Yes Ja - - + + No Nein - + Open settings Einstellungen öffnen - + New sets found Neue Editionen gefunden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5350,17 +5364,17 @@ Setcode(s): %1 Möchten Sie es/sie aktivieren? - + View sets Editionen ansehen - + Welcome Willkommen - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5369,65 +5383,65 @@ Alle Editionen der Kartendatenbank wurden aktiviert. Lesen Sie mehr über das Ändern der Editionsreihenfolge oder die Deaktivierung bestimmter Editionen im „Editionen verwalten...“ Fenster. - - + + Information Information - + A card database update is already running. Eine Datenbankaktualisierung wird bereits durchgeführt. - + Unable to run the card database updater: Kartendatenbankaktualisierung nicht ausführbar: - + Card database update running. Kartendatenbankaktualisierung läuft. - + Failed to start. The file might be missing, or permissions might be incorrect. Starten fehlgeschlagen. Die Datei könnte fehlen oder die Berechtigung fehlen. - + The process crashed some time after starting successfully. Der Prozess ist einige Zeit nach erfolgreichem Start abgestürzt. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Zeitüberschreitung. Der Prozess hat zu lange nicht geantwortet. Zeitüberschreitung bei letzter waitFor...() Funktion. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Ein Fehler ist beim Schreiben an den Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden oder er hat seinen Eingabekanal geschlossen. - + An error occurred when attempting to read from the process. For example, the process may not be running. Ein Fehler ist beim Lesen vom Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden. - + Unknown error occurred. Unbekannter Fehler aufgetreten. - + The card database updater exited with an error: %1 Die Kartendatenbankaktualisierung brach mit einem Fehler ab: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5438,55 +5452,55 @@ Dies stellt wahrscheinlich kein Problem dar, allerdings könnte dies bedeuten, d Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - - - - - + + + + + Load sets/cards Lade Sets/Karten - + Selected file cannot be found. Die ausgewählte Datei wurde nicht gefunden. - + You can only import XML databases at this time. Im Moment ist es nur möglich XML Datenbanken zu importieren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Die neuen Sets/Karten wurden erfolgreich hinzugefügt. Cockatrice wird jetzt die Kartendatenbank neu laden. - + Sets/cards failed to import. Set-/Kartenimport fehlgeschlagen. - - - + + + Reset Password Passwort zurücksetzen - + Your password has been reset successfully, you can now log in using the new credentials. Ihr Passwort wurde erfolgreich zurückgesetzt, Sie können sich jetzt mit den neuen Anmeldedaten anmelden. - + Failed to reset user account password, please contact the server operator to reset your password. Passwortzurücksetzung fehlgeschlagen. Bitte kontaktieren Sie den Serverbetreiber, um Ihr Passwort zurücksetzen zu lassen. - + Activation request received, please check your email for an activation token. Aktivierungsanfrage erhalten. Bitte prüfen Sie Ihre E-Mails nach einem Aktivierungstoken. @@ -5537,7 +5551,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ManaBaseWidget - + Mana Base Manabasis @@ -5691,590 +5705,610 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessageLogWidget - + from play vom Spielfeld - + from their graveyard von ihrem Friedhof - + from exile aus dem Exil - + from their hand von ihrer Hand - + the top card of %1's library die oberste Karte von %1s Bibliothek - + the top card of their library die oberste Karte ihrer Bibliothek - + from the top of %1's library oben von %1s Bibliothek - + from the top of their library von ihrer Bibliothek von oben - + the bottom card of %1's library die unterste Karte von %1s Bibliothek - + the bottom card of their library die unterste Karte ihrer Bibliothek - + from the bottom of %1's library von der Unterseite von %1s Bibliothek - + from the bottom of their library von ihrer Bibliothek von unten - + from %1's library aus %1s Bibliothek - + from their library von ihrer Bibliothek - + from sideboard aus dem Sideboard - + from the stack vom Stapel - + from custom zone '%1' von der benutzerdefinierten Zone '%1' - + %1 is now keeping the top card %2 revealed. %1 lässt nun die oberste Karte %2 aufgedeckt. - + %1 is not revealing the top card %2 any longer. %1 lässt die oberste Karte %2 nicht mehr aufgedeckt. - + %1 can now look at top card %2 at any time. %1 kann nun die oberste Karte %2 jederzeit betrachten. - + %1 no longer can look at top card %2 at any time. %1 kann die oberste Karte %2 nicht mehr jederzeit betrachten. - + %1 attaches %2 to %3's %4. %1 legt %2 an %3s %4 an. - + %1 has conceded the game. %1 hat das Spiel aufgegeben. - + %1 has unconceded the game. %1 hat das Spiel doch nicht aufgegeben. - + %1 has restored connection to the game. %1 ist wieder mit dem Spiel verbunden. - + %1 has lost connection to the game. %1 hat die Verbindung zum Spiel verloren. - + %1 points from their %2 to themselves. %1 zeigt von ihrem %2 auf sich selbst. - + %1 points from their %2 to %3. %1 zeigt von ihrem %2 auf %3. - + %1 points from %2's %3 to themselves. %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to %4. %1 zeigt von %2s %3 auf %4. - + %1 points from their %2 to their %3. %1 zeigt von ihrem %2 auf ihren %3. - + %1 points from their %2 to %3's %4. %1 zeigt von ihrem %2 auf %3s %4. - + %1 points from %2's %3 to their own %4. %1 zeigt von %2s %3 auf ihr %4. - + %1 points from %2's %3 to %4's %5. %1 zeigt von %2s %3 auf %4s %5. - + %1 creates a face down token. %1 erzeugt einen umgedrehten Spielstein. - + %1 creates token: %2%3. %1 erstellt Spielstein: %2%3. - + %1 has loaded a deck (%2). %1 hat ein Deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 hat ein Deck mit %2 Sideboardkarten geladen (%3). - + %1 destroys %2. %1 zerstört %2. - + a card eine Karte - + %1 gives %2 control over %3. %1 überlässt %2 die Kontrolle über %3. - + %1 puts %2 into play%3 face down. %1 bringt %2%3 verdeckt ins Spiel. - + %1 puts %2 into play%3. %1 bringt %2%3 ins Spiel. - + + %1 puts %2%3 into their graveyard face down. + %1 legt %2%3 verdeckt in ihren Friedhof + + + %1 puts %2%3 into their graveyard. %1 legt %2%3 in ihren Friedhof. + + + %1 exiles %2%3 face down. + %1 schickt %2%3 verdeckt ins Exil. + %1 exiles %2%3. %1 schickt %2%3 ins Exil. - + %1 moves %2%3 to their hand. %1 bewegt %2%3 in ihre Hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in ihre Bibliothek. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 unter die Bibliothek. - + %1 puts %2%3 on top of their library. %1 legt %2%3 auf ihre Bibliothek. - + %1 puts %2%3 into their library %4 cards from the top. %1 legt %2%3 in die Bibliothek an %4 Stelle von oben. - + %1 moves %2%3 to sideboard. %1 legt %2%3 in sein Sideboard. - + + %1 plays %2%3 face down. + %1 bringt %2%3 verdeckt ins Spiel. + + + %1 plays %2%3. %1 spielt %2%3 aus. - + + %1 moves %2%3 to custom zone '%4' face down. + %1 bewegt %2%3 verdeckt zur benutzerdefinierten Zone '%4'. + + + %1 moves %2%3 to custom zone '%4'. %1 bewegt %2%3 zur benutzerdefinierten Zone '%4'. - + %1 tries to draw from an empty library %1 versucht von einer leeren Bibliothek zu ziehen - + %1 draws %2 card(s). %1 zieht %2 Karte(n).%1 zieht %2 Karte(n). - + %1 is looking at %2. %1 sieht sich %2 an. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sieht sich die %4 %3 Karte(n) %2 an.%1 sieht sich die %4 %3 Karte(n) %2 an. - + bottom untersten - + top obersten - + %1 turns %2 face-down. %1 wendet %2 auf die Rückseite. - + %1 turns %2 face-up. %1 wendet %2 auf die Vorderseite. - + The game has been closed. Das Spiel wurde geschlossen. - + The game has started. Das Spiel hat begonnen. - + You are flooding the game. Please wait a couple of seconds. Du überschwemmst das Spiel. Warte bitte einige Sekunden. - + %1 has joined the game. %1 ist dem Spiel beigetreten. - + %1 is now watching the game. %1 schaut nun dem Spiel zu. - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. - + %1 has left the game (%2). %1 hat das Spiel verlassen (%2). - + %1 is not watching the game any more (%2). %1 schaut dem Spiel nicht mehr zu (%2). - + %1 is not ready to start the game any more. %1 ist nicht mehr bereit, das Spiel zu starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischt sein Deck und zieht eine Hand mit %2 Karte(n).%1 mischt sein Deck und zieht eine neue Hand mit %2 Karte(n). - + %1 shuffles their deck and draws a new hand. %1 mischt sein Deck und zieht eine neue Hand. - + You are watching a replay of game #%1. Sie sehen eine Aufzeichnung des Spiels #%1. - + %1 is ready to start the game. %1 ist bereit, das Spiel zu starten. - + cards an unknown amount of cards Karten - + %1 card(s) a card for singular, %1 cards for plural %1 Karte(n)%1 Karte(n) - + %1 lends %2 to %3. %1 verleiht %2 an %3. - + %1 reveals %2 to %3. %1 zeigt %3 %2. - + %1 reveals %2. %1 zeigt %2 offen vor. - + %1 randomly reveals %2%3 to %4. %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3. %1 zeigt zufällig %2%3 offen vor. - + %1 peeks at face down card #%2. %1 schaut sich die umgedrehte Karte #%2 an. - + %1 peeks at face down card #%2: %3. %1 schaut sich die umgedrehte Karte #%2 an: %3. - + %1 reveals %2%3 to %4. %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3. %1 zeigt %2%3 offen vor. - + %1 reversed turn order, now it's %2. %1 kehrte die Zugreihenfolge um, nun ist es %2. - + reversed umgekehrt - + normal normal - + Heads Kopf - + Tails Zahl - + %1 flipped a coin. It landed as %2. %1 warf eine Münze. Es fiel %2. - + %1 rolls a %2 with a %3-sided die. %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 wirft %2 Münzen. Es ergab %3 mal Kopf und %4 mal Zahl. - + %1 rolls a %2-sided dice %3 times: %4. %1 wirft einen %2-seitigen Würfel %3 mal: %4. - + %1's turn. %1 ist am Zug. - + %1 sets annotation of %2 to %3. %1 versieht %2 mit dem Hinweis %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 platziert %2 "%3" Zähler auf %4 (jetzt %5).%1 platziert %2 "%3" Zähler auf %4 (jetzt %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 entfernt %2 "%3" Marke(n) von %4 (jetzt %5).%1 entfernt %2 "%3" Zähler von %4 (jetzt %5). - + %1 sets counter %2 to %3 (%4%5). %1 setzt Zähler %2 auf %3 (%4%5). - + %1 sets %2 to not untap normally. %1 setzt %2 auf explizites Enttappen. - + %1 sets %2 to untap normally. %1 setzt %2 auf normales Enttappen. - + %1 removes the PT of %2. %1 entfernt die Kampfwerte von %2. - + %1 changes the PT of %2 from nothing to %4. %1 ändert die Kampfwerte von %2 von nichts auf %4. - + %1 changes the PT of %2 from %3 to %4. %1 ändert die Kampfwerte von %2 von %3 auf %4. - + %1 has locked their sideboard. %1 hat ihr Sideboard gesperrt. - + %1 has unlocked their sideboard. %1 hat ihr Sideboard entsperrt. - + %1 taps their permanents. %1 tappt ihre bleibenden Karten. - + %1 untaps their permanents. %1 enttappt ihre bleibenden Karten. - + %1 taps %2. %1 tappt %2. - + %1 untaps %2. %1 enttappt %2. - + %1 shuffles %2. %1 mischt %2. - + %1 shuffles the bottom %3 cards of %2. %1 mischt die untersten %3 Karten von %2. - + %1 shuffles the top %3 cards of %2. %1 mischt die obersten %3 Karten von %2. - + %1 shuffles cards %3 - %4 of %2. %1 mischt die Karten %3 - %4 von %2. - + %1 unattaches %2. %1 löst %2 ab. - + %1 undoes their last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes their last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2) @@ -6282,110 +6316,110 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessagesSettingsPage - + Word1 Word2 Word3 Wort1 Wort2 Wort3 - + Add New Message Neue Nachricht hinzufügen - + Edit Message Nachricht bearbeiten - + Remove Message Nachricht entfernen - + Add message Nachricht hinzufügen - - + + Message: Nachricht: - + Edit message Nachricht bearbeiten - + Chat settings Chat Einstellungen - + Custom alert words Benutzerdefinierte Benachrichtigungswörter - + Enable chat mentions Chat Erwähnungen aktivieren - + Enable mention completer Autovervollständigung aktivieren - + In-game message macros Makros für Nachrichten in Spielen - + How to use in-game message macros Anleitung zum Verwenden der Makros für Nachrichten in Spielen - + Ignore chat room messages sent by unregistered users Nachrichten von unregistrierten Benutzern im Chatroom ignorieren - + Ignore private messages sent by unregistered users Private Nachrichten von unregistrierten Benutzern ignorieren - - + + Invert text color Textfarbe invertieren - + Enable desktop notifications for private messages Desktop Benachrichtigungen für private Nachrichten aktivieren - + Enable desktop notification for mentions Desktop Benachrichtigungen für Erwähnungen aktivieren - + Enable room message history on join Nachrichtenverlauf beim Betreten eines Raumes aktivieren - - + + (Color is hexadecimal) (Farbcode in hexadezimal) - + Separate words with a space, alphanumeric characters only Wörter durch Leerzeichen trennen, nur alphanumerische Zeichen @@ -6398,32 +6432,37 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Bewegen nach - + &Top of library in random order &Oben auf der Bibliothek in zufälliger Reihenfolge - + X cards from the top of library... X Karten von oben in der Bibliothek... - + &Bottom of library in random order &Unter die Bibliothek in zufälliger Reihenfolge - + + T&able + T&isch + + + &Hand &Hand - + &Graveyard &Friedhof - + &Exile &Exil @@ -6565,57 +6604,57 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PhasesToolbar - + Untap step Enttappsegment - + Upkeep step Versorgungssegment - + Draw step Ziehsegment - + First main phase erste Hauptphase - + Beginning of combat step Anfangssegment der Kampfphase - + Declare attackers step Angreifer-Deklarieren-Segment - + Declare blockers step Blocker-Deklarieren-Segment - + Combat damage step Kampfschadenssegment - + End of combat step Endsegment der Kampfphase - + Second main phase zweite Hauptphase - + End of turn step Ende-des-Zuges-Segment @@ -6632,134 +6671,138 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PlayerActions - + View top cards of library Oberste Karten der Bibliothek ansehen - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Anzahl der Karten: (max. %1) - + View bottom cards of library Unterste Karten der Bibliothek ansehen - + Shuffle top cards of library Oberste Karten der Bibliothek mischen - + Shuffle bottom cards of library Unterste Karten der Bibliothek mischen - + Draw hand Hand ziehen - + 0 and lower are in comparison to current hand size 0 und kleiner sind im Vergleich zur jetzigen Handgröße - + Draw cards Karten ziehen + + + + + + grave + Friedhof + - Move top cards to grave - Oberste Karten in den Friedhof bewegen + + + + exile + Exil - - Move top cards to exile - Oberste Karten ins Exil bewegen + + Move top cards to %1 + Oberste Karten nach %1 bewegen - - Move bottom cards to grave - Unterste Karten in den Friedhof bewegen + + Move bottom cards to %1 + Unterste Karten nach %1 bewegen - - Move bottom cards to exile - Unterste Karten ins Exil bewegen - - - + Draw bottom cards Unterste Karten ziehen - - + + C&reate another %1 token Einen weiteren %1 Spielstein erzeugen - + Create tokens Spielsteine erzeugen - - + + Number: Anzahl: - + Place card X cards from top of library Karte X Karten von oben in der Bibliothek plazieren - + Which position should this card be placed: In welcher Position sollte diese Karte platziert werden: - + (max. %1) (max. %1) - + Change power/toughness Stärke/Widerstandskraft ändern - + Change stats to: Ändere Werte auf: - + Set annotation Anmerkung setzen - + Please enter the new annotation: Bitte die neue Anmerkung eingeben: - + Set counters Zähler setzen @@ -6767,48 +6810,68 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PlayerMenu - + Player "%1" Spieler "%1" - + &Counters &Zähler + + + PrintingDisabledInfoWidget - - S&ay - S&agen + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + Die Druckauswahl ist deaktiviert, da sie momentan die Einstellung zur Überschreibung aller ausgewählten Drucke mit ihren persönlichen Setpräferenzen aktiviert ist. + +Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen, anstatt einen bestimmten Druck auszuwählen, und sie werden auch nicht die von anderen Personen ausgewählten Drucke sehen können. + + + + + Enable printings again + Drucke wieder aktivieren PrintingSelector - + Display Navigation Buttons Navigationstasten anzeigen + + + Printing Selector + Druckauswahl + PrintingSelectorCardOverlayWidget - + Preference Preferenz - + Pin Printing Version festsetzen - + Unpin Printing Version lösen - + Show Related cards Zugehörige Karten anzeigen @@ -6857,17 +6920,25 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Erscheinungsdatum - - + + Descending Absteigend - + Ascending Aufsteigend + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + Wählen sie eine Karte aus, um ihre verfügbaren Drucke anzusehen + + PtMenu @@ -7006,6 +7077,45 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. A .cod version of this deck already exists. Overwrite it? Eine .cod Version dieses Decks existiert bereits. Überschreiben? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + Diese Funktion zu aktivieren wird die Nutzung der Druckauswahl deaktivieren. + +Es wird ihnen nicht möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis zu verwalten, oder die Drucke, die andere Personen für ihre Decks ausgewählt haben, zu sehen. + +Sie werden den Set-Manager verwenden müssen, der über Kartendatenbank -> Sets verwalten verfügbar ist. + +Sind sie sicher, dass diese Funktion aktiviert werden soll? + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + Diese Funktion zu deaktivieren wird die Nutzung der Druckauswahl aktivieren. + +Es wird ihnen nun möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis im Deck-Editor zu verwalten und zu konfigurieren, welcher Druck standardmäßig in ein Deck hinzugefügt wird, indem sie es anheften in der Druckauswahl + +Sie können auch den Set-Manager verwenden, um selbstständig die Sortierungsreihenfolge für Drucke in der Druckauswahl festzulegen (andere Sortierungsoptionen wie alphabetisch oder Veröffentlichkeitsdatum sind verfügbar). + +Sind sie sicher, dass diese Funktion deaktiviert werden soll? + + + + Confirm Change + Änderung bestätigen + QPlatformTheme @@ -7154,37 +7264,37 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. RfgMenu - + &Exile &Exil - + &View exile &Exil ansehen - + &Move exile to... &Exil nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &Hand &Hand - + &Graveyard &Friedhof @@ -7227,6 +7337,14 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Spiele + + SayMenu + + + S&ay + S&agen + + SequenceEdit @@ -7291,53 +7409,53 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ShortcutSettingsPage - - + + Restore all default shortcuts Alle Standard-Tastaturkürzel wiederherstellen - + Do you really want to restore all default shortcuts? Möchten Sie wirklich alle Standard-Tastaturkürzel wiederherstellen? - + Clear all default shortcuts Alle Standard-Tastaturkürzel entfernen - + Do you really want to clear all shortcuts? Möchten Sie wirklich alle Tastaturkürzel entfernen? - + Section: Abschnitt: - + Action: Aktion: - + Shortcut: Tastaturkürzel: - + How to set custom shortcuts Wie werden benutzerdefinierte Tastaturkürzel gesetzt - + Clear all shortcuts Alle Tastaturkürzel entfernen - + Search by shortcut name Suchen nach Name des Tastenkürzels @@ -7406,27 +7524,27 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SoundSettingsPage - + Enable &sounds Töne aktivieren - + Current sounds theme: Aktuelles Ton-Theme: - + Test system sound engine Systemsound testen - + Sound settings Toneinstellungen - + Master volume Masterlautstärke @@ -7642,59 +7760,123 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabArchidekt - - + + + Desc. Beschreibung. - + + + AND + UND + + + + + Require ALL selected colors + Benötigt ALLE ausgewählten Farben + + + + + Deck name... + Deckname... + + + + + Owner... + Besitzer... + + + + + Packages + Pakete + + + + + Advanced Filters + Erweiterte Filter + + + + Bracket: + Kategorie: + + + + + Any + Beliebig + + + + + Contains card... + Enthält Karte... + + + + + Commander... + Kommandant... + + + + + Tag... + Etikett... + + + + + Deck Size + Deckgröße + + + + Cards: + Karten: + + + + Asc. Aufsteigend. - - - Any Bracket - Jede Klammer + + Sort by: + Sortieren nach: - - Deck name contains... - Deckname enthält... + + Filter by: + Filtern nach: - - Owner name contains... - Besitzername enthält... + + Display Settings + Anzeigeeinstellungen - - Disabled - Deaktiviert - - - + + Search Suche - + + Formats Formate - - Min. # of Cards: - Min. # an Karten: - - - - Page: - Seite: - - - + Archidekt: Archidekt: @@ -7702,60 +7884,52 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditor - + Card Info Karteninformationen - + Deck Deck - + Filters Filter - + &View Ansicht - + Card Database Kartendatenbank - + Printing Auflage - - - - - + Visible Sichtbar - - - - - + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Deck: %1 Deck: %1 @@ -7763,61 +7937,55 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditorVisual - + Visual Deck: %1 Visuelles Deck: %1 - + &Visual Deck Editor &Visueller Deckeditor - - + + Card Info Karteninformation - - + + Deck Deck - - + + Filters Filter - + &View &Ansehen - + Printing Auflage - - - - + Visible Sichtbar - - - - + Floating Schwebend - + Reset layout Anordnung zurücksetzen @@ -7882,7 +8050,7 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! - + New folder Neuer Ordner @@ -7964,18 +8132,18 @@ Bitte geben Sie einen Namen ein: Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + Delete remote decks Löschen der online gespeicherten Decks - + Are you sure you want to delete the selected decks? Sind sie sicher, dass sie die ausgewählten Decks löschen wollen? - + Name of new folder: Name für den neuen Ordner: @@ -7993,12 +8161,12 @@ Bitte geben Sie einen Namen ein: Visuelle Deckablage - + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden @@ -8047,197 +8215,191 @@ Bitte geben Sie einen Namen ein: TabGame - - - + + + Replay Wiederholung - - + + Game Spiel - - + + Player List Spielerliste - - + + Card Info Karteninformationen - - + + Messages Nachrichten - - + + Replay Timeline Zeitleiste der Aufzeichnung - + &Phases &Phasen - + &Game Spi&el - + Next &phase Nächste &Phase - + Next phase with &action Nächste Phase mit &Aktion - + Next &turn Nächster &Zug - + Reverse turn order Kehre Zugreihenfolge um - + &Remove all local arrows &Lokale Pfeile entfernen - + Rotate View Cl&ockwise Ansicht im Uhrzeigesinn drehen - + Rotate View Co&unterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Game &information &Spielinformationen - + Un&concede Aufgabe zurückziehen - - - + + + &Concede &Aufgeben - + &Leave game Spiel ver&lassen - + C&lose replay Wiederholung sch&ließen - + &Focus Chat Chat &fokussieren - + &Say: &Sagen: - + Selected cards Ausgewählte Karten - + &View Ansicht - - - - + Visible Sichtbar - - - - + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Concede Aufgeben - + Are you sure you want to concede this game? Sind Sie sicher, dass Sie das Spiel aufgeben möchten? - + Unconcede Doch nicht aufgeben - + You have already conceded. Do you want to return to this game? Sie haben bereits aufgegeben. Möchten Sie zu diesem Spiel zurückkehren? - + Leave game Spiel verlassen - + Are you sure you want to leave this game? Sind Sie sicher, dass Sie das Spiel verlassen möchten? - + A player has joined game #%1 Ein Spieler ist Spiel #%1 beigetreten - + %1 has joined the game %1 ist dem Spiel beigetreten - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. @@ -9340,142 +9502,152 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserInterfaceSettingsPage - + General interface settings Allgemeine Bedienung - + &Double-click cards to play them (instead of single-click) Karten durch &Doppelklick ausspielen (statt Einzelklick) - + &Clicking plays all selected cards (instead of just the clicked card) &Anklicken spielt alle ausgewählten Karten aus (anstatt nur der angeklickten Karte) - + &Play all nonlands onto the stack (not the battlefield) by default Alle Nichtländer standardmäßig über den Stapel spielen (anstatt direkt auf das Spielfeld) - + Do not delete &arrows inside of subphases Keine &Pfeile löschen in Unterphasen - + Close card view window when last card is removed Schließe Kartenanzeigefenster, wenn die letzte Karte entfernt wird - + Auto focus search bar when card view window is opened Sucheingabe automatisch in den Fokus nehmen, wenn das Kartenansichtsfenster geöffnet wird - + Annotate card text on tokens Kartentext auf Spielsteinen anzeigen - + + Show selection counter during drag selection + Auswahlzähler während Zugauswahl anzeigen + + + + Show total selection counter + Gesamtauswahlszähler anzeigen + + + Use tear-off menus, allowing right click menus to persist on screen Benutze Abreißmenüs, erlaubt Kontextmenüs auf dem Bildschirm zu verbleiben - + Notifications settings Benachrichtigungseinstellungen - + Enable notifications in taskbar Benachrichtigungen in der Taskleiste aktivieren - + Notify in the taskbar for game events while you are spectating Benachrichtigungen für Spielereignisse auch beim Zuschauen anderer Spiele in der Taskbar anzeigen - + Notify in the taskbar when users in your buddy list connect Benachrichtige in der Taskleiste wenn sich Benutzer aus der Freundesliste anmelden - + Animation settings Animationseinstellungen - + &Tap/untap animation Animiertes &Tappen/Enttappen - + Deck editor/storage settings Deckeditor/-ablage Einstellungen - + Open deck in new tab by default Decks in neuem Tab öffnen als Standard - + Use visual deck storage in game lobby Visuelle Deckablage in der Spielelobby verwenden - + Use selection animation for Visual Deck Storage Auswahlanimation für Visuellen Deckspeicher verwenden - + When adding a tag in the visual deck storage to a .txt deck: Wenn ein Tag im Visuellen Deckspeicher zu einem .txt Deck hinzugefügt wird: - + do nothing tue nichts - + ask to convert to .cod frage, ob zu .cod konvertiert werden soll - + always convert to .cod immer nach .cod konvertieren - + Default deck editor type Standarddeckeditortyp - + Classic Deck Editor Klassischer Deckeditor - + Visual Deck Editor Visueller Deckeditor - + Replay settings Wiederholungsoptionen - + Buffer time for backwards skip via shortcut: Pufferzeit für Rückwärtsüberspringen durch Tastenkürzel @@ -9539,24 +9711,25 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match - Modus: Exakte Übereinstimmung + + Exact match + Exakte Übereinstimmung + + + + Includes + Enthält - Mode: Includes - Modus: Enthält + Include / Exclude + Mode: Includes + Inkludieren / Exkludieren - - Mode: Include/Exclude - Modus: Inkludieren/Exkludieren - - - - Filter mode (AND/OR/NOT conjunctions of filters) - Filtermodus (UND/ODER/NICHT Verknüpfungen der Filter) + + How selected and unselected colors are combined in the filter + Wie ausgewählte und nicht ausgewählte Farben im Filter kombiniert werden @@ -9582,25 +9755,108 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Dateinamen eingeben... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + Sortieren nach + + + + Filter by + Filtern nach + + + + Save and load filters + Filter speichern und laden + + + + Filter by exact card name + Nach exaktem Kartennamen filtern + + + + Filter by card main-type + Nach Kartenhaupttyp filtern + + + + Filter by card sub-type + Nach Kartenuntertyp filtern + + + + Filter by set + Nach Set filtern + + + + Filter by format legality + Nach Formatslegalität filtern + + + + Save/Load + Speichern/Laden + + + + Name + Name + + + + Main Type + Haupttyp + + + + Sub Type + Untertyp + + + + Sets + Editionen + + + + Formats + Formate + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + Formate anzeigen mit mindestens: + + + + cards + Karten + + + Do not display formats with less than this amount of cards in the database Keine Formate mit weniger als dieser Anzahl an Karten in der Datenbank anzeigen - + Filter mode (AND/OR/NOT conjunctions of filters) Filtermodus (UND/ODER/NICHT Verbindungen von Filtern) - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält @@ -9608,22 +9864,32 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + Haupttypen anzeigen mit mindestens: + + + + cards + Karten + + + Do not display card main-types with less than this amount of cards in the database Kartenhaupttypen mit weniger als dieser Anzahl Karten in der Datenbank nicht anzeigen - + Filter mode (AND/OR/NOT conjunctions of filters) Filtermodus (UND/ODER/NICHT Verknüpfungen der Filter) - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält @@ -9659,7 +9925,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtern nach aktuellsten Sets @@ -9667,19 +9933,19 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplaySetFilterWidget - + Search sets... Sets durchsuchen... - - + + Mode: Exact Match Modus: Exakte Übereinstimmung - - + + Mode: Includes Modus: Enthält @@ -9687,27 +9953,37 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... Untertypen durchsuchen... - + + Show sub types with at least: + Untertypen anzeigen mit mindestens: + + + + cards + Karten + + + Do not display card sub-types with less than this amount of cards in the database Kartenuntertypen mit weniger als dieser Anzahl Karten in der Datenbank nicht anzeigen - + Filter mode (AND/OR/NOT conjunctions of filters) Filtermodus (UND/ODER/NICHT Verknüpfungen der Filter) - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält @@ -9721,52 +9997,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - + Visual Visuell - + Loading database ... Datenbank wird geladen... - + Clear all filters Alle Filter entfernen - - Sort by: - Sortieren nach: - - - - Filter by: - Filtern nach: - - - - Save and load filters - Filter speichern und laden - - - - Filter by exact card name - Nach exaktem Kartennamen filtern - - - - Filter by card sub-type - Nach Kartenuntertyp filtern - - - - Filter by set - Nach Set filtern - - - + Table Tisch @@ -9774,56 +10020,64 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckDisplayOptionsWidget - + Group by: Gruppieren nach: - + Change how cards are divided into categories/groups. Wie Karten innerhalb ihrer Kategorien/Gruppierungen sortiert werden ändern - + Sort by: Sortieren nach: - + Click and drag to change the sort order within the groups Klicken und ziehen, um die Sortierungsreihenfolge innerhalb von Gruppen zu ändern - + Configure how cards are sorted within their groups Wie Karten innerhalb ihrer Gruppierung sortiert werden einstellen - - + + Toggle Layout: Overlap Umschalten des Layouts: Überlappung - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Wie Karten innerhalb von Zonen angezeigt werden ändern ( z.B. überlappend oder vollständig sichtbar) - + Toggle Layout: Flat Umschalten des Layouts: Flach + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + Karten über die Suchleiste oder den Datenbanktab hinzufügen, damit sie hier angezeigt werden + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Eine neue Probehand ziehen - + Sample hand size Probehandgröße @@ -9831,17 +10085,17 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Hier einen Kartennahmen eingeben für Vorschläge aus der Datenbank... - + Quick search and add card Schnellsuche und Karte hinzufügen - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Nach bester Entsprechung in der Datenbank suchen (mit automatischen Vorschlägen) und die bevorzugte Auflage beim Betätigen der Eingabetaste dem Deck hinzufügen @@ -9857,47 +10111,52 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageQuickSettingsWidget - + Show Folders Ordner anzeigen - + Show Tag Filter Tagfilter anzeigen - + + Show Color Identity + Farbidentität anzeigen + + + Show Tags On Deck Previews Tags auf Deckvorschauen anzeigen - + Show Banner Card Selection Option Bannerkartenauswahloption anzeigen - + Draw unused Color Identities Ungenutzte Farbidentitäten ziehen - + Unused Color Identities Opacity Deckkraft ungenutzter Farbidentitäten - + Deck tooltip: Decktooltip: - + None Keine - + Filepath Dateipfad @@ -9998,133 +10257,133 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie WndSets - + Move selected set to the top Ausgewählte Edition an die Spitze verschieben - + Move selected set up nach oben verschieben - + Move selected set down nach unten verschieben - + Move selected set to the bottom Ausgewählte Edition ans Ende verschieben - + Search by set name, code, or type Suche nach Setname, Code oder Typ - + Default order Standardreihenfolge - + Restore original art priority order Stelle die ursprüngliche Reihenfolge für Bilderprioritäten wieder her - + Enable all sets Alle Editionen aktivieren - + Disable all sets Alle Editionen deaktivieren - + Enable selected set(s) Ausgewählte Edition(en) aktivieren - + Disable selected set(s) Ausgewählte Edition(en) deaktivieren - + Deck Editor Deckeditor - + Use CTRL+A to select all sets in the view. Benutzen Sie Strg+A, um alle Sets in der Ansicht auszuwählen. - + Only cards in enabled sets will appear in the card list of the deck editor. Nur Karten aus aktivierten Sets werden in der Kartenliste des Deckeditors angezeigt. - + Image priority is decided in the following order: Bildpriorität wird auf folgende Reihenfolge festgelegt: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki zuerst der BENUTZERDEFINIERTE Ordner (%1), dann die aktivierten Sets aus diesem Dialog (von oben nach unten) - + Include cards rebalanced for Alchemy [requires restart] Karten, die für Alchemy augeglichen wurden, einschließen [erfordert Neustart] - + Card Art Kartenzeichnung - + How to use custom card art Wie benutzerdefinierte Kartenbilder verwendet werden - + Hints Tipps - + Note Notiz - + Sorting by column allows you to find a set while not changing set priority. Nach einer Spalte sortieren erlaubt, eine Edition zu finden, ohne die Editionspriorität zu verändern. - + To enable ordering again, click the column header until this message disappears. Um die Sortierung erneut zu aktivieren, klicken Sie auf den Spaltenkopf bis diese Nachricht verschwindet. - + Use the current sorting as the set priority instead Benutze die momentane Sortierung als Editionspriorität. - + Sorts the set priority using the same column Sortiert die Editionspriorität mit der selben Spalte - + Manage sets Editionen verwalten @@ -10132,72 +10391,72 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie ZoneViewWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + Ungrouped Ungruppiert - + Group by Type Gruppieren nach Kartentyp - + Group by Mana Value Gruppieren nach Manabetrag - + Group by Color Gruppieren nach Farbe - + Unsorted Unsortiert - + Sort by Name Sortieren nach Name - + Sort by Type Sortieren nach Kartentyp - + Sort by Mana Cost Sortieren nach Manakosten - + Sort by Colors Sortieren nach Farben - + Sort by P/T Sortieren nach P/T - + Sort by Set Sortieren nach Auflage - + shuffle when closing beim Schließen mischen - + pile view Stapelansicht @@ -10232,7 +10491,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - + Deck Editor Deckeditor @@ -10313,7 +10572,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - + Replays Wiederholungen @@ -10465,7 +10724,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - + Reset Layout Darstellung zurücksetzen @@ -10886,8 +11145,9 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - Toggle Untap - Enttappen umschalten + Toggle Skip Untapping + Toggle Untap + Enttapen überspringen umschalten @@ -10906,98 +11166,102 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie + Play Card, Face Down + Karte spielen, verdeckt + + + Attach Card... Karte anlegen... - + Unattach Card Karte lösen - + Clone Card Karte klonen - + Create Token... Spielstein erstellen... - + Create All Related Tokens Alle zugehörigen Spielsteine erstellen - + Create Another Token Einen weiter Spielstein erstellen - + Set Annotation... Notiz setzen... - + Select All Cards in Zone Alle Karten in der Zone auswählen - + Select All Cards in Row Alle Karten in der Reihe auswählen - + Select All Cards in Column Alle Karten in der Spalte auswählen - + Reveal Selected Cards to All Players Ausgewählte Karten allen Spielern vorzeigen - - + + Bottom of Library Untere Karte der Bibliothek - + - - + + Exile Exil - + - + Graveyard Friedhof - + Hand Hand - - + + Top of Library Obere Karte der Bibliothek - - + Battlefield, Face Down Spielfeld, verdeckt @@ -11033,234 +11297,246 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie - + Stack Stapel - + Graveyard (Multiple) Friedhof (mehrere) - - + + + Graveyard (Multiple), Face Down + Friedhof (mehrere), verdeckt + + + + Exile (Multiple) Exil (mehrere) - + + + Exile (Multiple), Face Down + Exil (mehrere), verdeckt + + + Stack Until Found Stapel bis gefunden - + Draw Bottom Card Unterste Karte ziehen - + Draw Multiple Cards from Bottom... Mehrere Karten von unten ziehen... - + Draw Arrow... Pfeil zeichnen... - + Remove Local Arrows Lokale Pfeile entfernen - + Leave Game Spiel verlassen - + Concede Aufgeben - + Roll Dice... Würfeln... - + Shuffle Library Bibliothek mischen - + Shuffle Top Cards of Library Oberste Karten der Bibliothek mischen - + Shuffle Bottom Cards of Library Unterste Karten der Bibliothek mischen - + Mulligan Mulligan - + Mulligan (Same hand size) Mulligan nehmen (Gleiche Handgröße) - + Mulligan (Hand size - 1) Mulligan nehmen (Handgröße -1) - + Draw a Card Eine Karte ziehen - + Draw Multiple Cards... Mehrere Karten ziehen... - + Undo Draw Ziehen rückgängig machen - + Always Reveal Top Card Oberste Karte aufgedeckt lassen - + Always Look At Top Card Oberste Karte immer betrachten - + Sort Hand by Name Hand sortieren nach Name - + Sort Hand by Type Hand sortieren nach Typ - + Sort Hand by Mana Value Hand sortieren nach Manawert - + Reveal Hand to All Players Hand allen Spielern vorzeigen - + Reveal Random Card to All Players Zufällige Karte allen Spielern vorzeigen - + Rotate View Clockwise Ansicht im Uhrzeigersinn drehen - + Rotate View Counterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Unfocus Text Box Textboxfokus aufheben - + Focus Chat Chat fokussieren - + Clear Chat Chat leeren - + Refresh Aktualisieren - + Skip Forward Vorspulen - + Skip Backward Zurückspulen - + Skip Forward by a lot Sehr weit vorspulen - + Skip Backward by a lot Sehr weit zurückspulen - + Play/Pause Abspielen/Pausieren - + Toggle Fast Forward Schnelldurchlauf umschalten - + Home Zuhause - + Visual Deck Storage Visuelle Deckablage - + Deck Storage Deckablage - + Server Server - + Account Benutzerkonto - + Administration Administration - + Logs Logs diff --git a/cockatrice/translations/cockatrice_el.ts b/cockatrice/translations/cockatrice_el.ts index dac77fe01..5bd3b2826 100644 --- a/cockatrice/translations/cockatrice_el.ts +++ b/cockatrice/translations/cockatrice_el.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Σφάλμα - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Ρυθμίσεις θέματος - + Current theme: Τρέχον θέμα: - + Open themes folder Άνοιγμα φακέλου θεμάτων - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Απόδοση κάρτας - + Display card names on cards having a picture Εμφανίστε τα ονόματα καρτών σε κάρτες με εικόνα - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Αύξηση της κλίμακας των καρτών με την επιλογή του ποντικιού - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Διάταξη χειρός - + Display hand horizontally (wastes space) Εμφάνιση χεριού οριζόντια (σπατάλη χώρου) - + Enable left justification Ενεργοποιήστε την αριστερή αιτιολόγηση - + Table grid layout Διάταξη πλέγματος πίνακα - + Invert vertical coordinate Ανατροπή κάθετης συντεταγμένης - + Minimum player count for multi-column layout: Ελάχιστος αριθμός παικτών για διάταξη πολλαπλών στηλών: - + Maximum font size for information displayed on cards: Μέγιστο μέγεθος γραμματοσειράς για πληροφορίες που εμφανίζονται στις κάρτες: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success Επιτυχία - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Σφάλμα - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count - + Set - + Number Αριθμός - + Provider ID - + Card Κάρτα @@ -1545,12 +1528,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ This is only saved for moderators and cannot be seen by the banned person. - - + + Error Σφάλμα - + The selected file could not be loaded. Δεν ήταν δυνατή η φόρτωση του επιλεγμένου αρχείου. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Φόρτωση deck από το clipboard - + Error Σφάλμα - + Invalid deck list. Μη έγκυρο deck list @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Φόρτωση deck + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3167,7 +3183,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3184,7 +3200,7 @@ Would you like to change your database location setting? Θα θέλατε να αλλάξετε τη ρυθμισμένη θέση της βάσης δεδομένων σας; - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3193,21 +3209,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3216,59 +3232,59 @@ Would you like to change your database location setting? - - - + + + Error Σφάλμα - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Ρυθμίσεις - + General Γενικά - + Appearance Εμφάνιση - + User Interface Διεπαφή Χρήστη - + Card Sources - + Chat - + Sound Ήχος - + Shortcuts Συντομεύσεις @@ -3581,67 +3597,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4089,143 +4105,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Επιλέξτε θέση - + Personal settings - + Language: Γλώσσα: - + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: Φάκελος deck: - + Filters directory: - + Replays directory: Φάκελος replay: - + Pictures directory: Φάκελος εικόνων: - + Card database: Βάση δεδομένων καρτών - + Custom database directory: - + Token database: Βάση δεδομένων token: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4233,47 +4249,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4281,88 +4297,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4370,52 +4386,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4423,193 +4439,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4700,18 +4736,8 @@ Will now login. - - Number of players - - - - - Please enter the number of players. - - - - - + + Player %1 @@ -4814,8 +4840,8 @@ Will now login. - - + + Error Σφάλμα @@ -5216,144 +5242,144 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Βάση δεδομένων καρτών - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Ναι - - + + No Όχι - + Open settings Άνοιγμα ρυθμίσεων - + New sets found Βρέθηκαν νέα σετ - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Εμφάνιση σετ - + Welcome Καλωσήρθατε - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Πληροφορίες - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5361,54 +5387,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Φόρτωση σετ/καρτών - + Selected file cannot be found. Το επιλεγμένο αρχείο δε βρέθηκε. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5459,7 +5485,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5613,590 +5639,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play από το παιχνίδι - + from their graveyard - + from exile από την εξορία - + from their hand - + the top card of %1's library την πρώτη κάρτα της βιβλιοθήκης του/της %1 - + the top card of their library - + from the top of %1's library από την κορυφή της βιβλιοθήκης του/της %1 - + from the top of their library - + the bottom card of %1's library την τελευταία κάρτα της βιβλιοθήκης του/της %1 - + the bottom card of their library - + from the bottom of %1's library από το τέλος της βιβλιοθήκης του/της %1 - + from the bottom of their library - + from %1's library από τη βιβλιοθήκη του/της %1 - + from their library - + from sideboard από το sideboard - + from the stack από το stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). Ο/Η %1 φόρτωσε ένα deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Ο/Η %1 φόρτωσε ένα deck με %2 κάτρες στο sideboard (%3). - + %1 destroys %2. - + a card μία κάρτα - + %1 gives %2 control over %3. ο/η %1 δίνει στον/στην %2 τον έλεγχο του %3 . - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. ο/η %1 βάζει στο παιχνίδι το %2%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. ο/η %1 εξορίζει το %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Το παιχνίδι έχει κλείσει - + The game has started. Το παιχνίδι ξεκίνησε. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. Ο/Η %1 παρακολουθεί το παιχνίδι. - + You have been kicked out of the game. Σας απέβαλαν από το παιχνίδι. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6204,110 +6250,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6320,32 +6366,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6487,57 +6538,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -6554,134 +6605,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6689,48 +6744,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6779,17 +6851,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6928,6 +7008,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7076,37 +7183,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7149,6 +7256,14 @@ Cockatrice will now reload the card database. + + SayMenu + + + S&ay + + + SequenceEdit @@ -7213,53 +7328,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7326,27 +7441,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7562,59 +7677,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7622,60 +7801,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 @@ -7683,61 +7854,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7802,7 +7967,7 @@ Please check your shortcut settings! - + New folder @@ -7883,18 +8048,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: @@ -7912,12 +8077,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7966,197 +8131,191 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9250,142 +9409,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9449,23 +9618,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9492,25 +9662,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9518,22 +9771,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9569,7 +9832,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9577,19 +9840,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9597,27 +9860,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9631,52 +9904,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9684,56 +9927,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9741,17 +9992,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9767,47 +10018,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9908,133 +10164,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10042,72 +10298,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -10142,7 +10398,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10223,7 +10479,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10375,7 +10631,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10796,7 +11052,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10816,98 +11073,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players - - - - Exile + + Bottom of Library + + + + Exile + + + + - + Graveyard - + Hand - - + + Top of Library - - + Battlefield, Face Down @@ -10943,234 +11204,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index e80e28b52..53fc9ac61 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Actualizar - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab Abrir en nueva pestaña - + Are you sure? ¿Estás seguro? - + The decklist has been modified. Do you want to save the changes? La lista del mazo ha sido modificada. ¿Deseas guardar los cambios? - - - - - - + + + + + + Error Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Guardar mazo - + The deck could not be saved. El mazo no puede ser guardado. - + There are no cards in your deck to be exported No hay cartas en tu mazo para exportar @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds segundos - + Error Error - + Could not create themes directory at '%1'. No se pudo crear directorio de temas en '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Preferencias del tema - + Current theme: Tema actual: - + Open themes folder Abrir carpeta de temas - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled Deshabilitado - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Representación de las cartas - + Display card names on cards having a picture Mostrar nombre de las cartas en aquellas que tengan imagen - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Cambiar tamaño de las cartas al pasar el ratón por encima - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows filas - + Maximum expanded height for card view window: - + Card counters Contadores de carta - + Counter %1 - + Hand layout Disposición de la mano - + Display hand horizontally (wastes space) Mostrar la mano horizontalmente (desperdicia espacio) - + Enable left justification Habilitar justificación a la izquierda - + Table grid layout Diseño de la cuadrícula de la mesa - + Invert vertical coordinate Invertir coordenada vertical - + Minimum player count for multi-column layout: Número minimo de jugadores para usar la cuadrícula multicolumna: - + Maximum font size for information displayed on cards: Tamaño de fuente máxima para la información presentada en las cartas: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoPictureWidget - + View related cards Ver cartas relacionadas - + Add card to deck Añadir carta al mazo - + Mainboard Mazo principal - + Sideboard Banquillo @@ -693,124 +676,124 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardMenu - + Re&veal to... - + &All players &Todos los jugadores - + View related cards Ver cartas relacionadas - + Token: Ficha: - + All tokens Todas las fichas - + &Select All &Seleccionar todo - + S&elect Row &Seleccionar fila - + S&elect Column &Seleccionar columna - + &Play &Jugar - + &Hide &Ocultar - + Play &Face Down Jugar boca abajo - + &Tap / Untap Turn sideways or back again &Girar / Enderezar - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone &Clonar - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) &Añadir contador (%1) - + &Remove counter (%1) &Quitar contador (%1) - + &Set counters (%1)... &Establecer contadores (%1) @@ -826,133 +809,133 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardZoneLogic - + their hand nominative su mano - + %1's hand nominative - + their library look at zone su biblioteca - + %1's library look at zone - + of their library top cards of zone, de su biblioteca - + of %1's library top cards of zone - + their library reveal zone su biblioteca - + %1's library reveal zone - + their library shuffle su biblioteca - + %1's library shuffle - - - their library - nominative - su biblioteca - - - - %1's library - nominative - - + their library + nominative + su biblioteca + + + + %1's library + nominative + + + + their graveyard nominative su cementerio - + %1's graveyard nominative - + their exile nominative su zona de exilio - + %1's exile nominative - - - their sideboard - look at zone - su banquillo - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone su banquillo %1's sideboard + look at zone + + + + + their sideboard + nominative + su banquillo + + + + %1's sideboard nominative - + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorCardInfoDockWidget - + Card Info Información de la carta @@ -1045,32 +1028,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Añadir al Banquillo - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards Mostrar cartas Relacionadas - + Add card to &maindeck Añadir carta al &mazo principal - + Add card to &sideboard Añadir carta al &banquillo @@ -1078,32 +1061,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type Tipo principal de carta - + Mana Cost Coste de maná - + Colors Colores - + Select Printing @@ -1176,17 +1159,17 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpiar todos los filtros - + Delete selected Borrar seleccionado @@ -1309,7 +1292,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorSettingsPage - - + + Update Spoilers Actualizar Spoilers - - + + Success Éxito - + Download URLs have been reset. Las URL de descarga se han reiniciado. - + Downloaded card pictures have been reset. Las imágenes de las cartas descargadas se han reiniciado. - + Error Error - + One or more downloaded card pictures could not be cleared. Una o más imágenes descargadas pueden no ser claras - + Add URL Añadir URL - - + + URL: URL: - - + + Edit URL Editar URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL Añadir URL - + Remove URL Borrar URL - + Day(s) Día(s) - + Updating... Actualizando... - + Choose path Elegir camino - + URL Download Priority Prioridad de descarga de URL - + Spoilers Spoilers - + Download Spoilers Automatically Descargar Spoilers automáticamente - + Spoiler Location: Localización del Spoiler: - + Last Change Último cambio - + Spoilers download automatically on launch Spoilers se descargan automáticamente en lanzamiento - + Press the button to manually update without relaunching Presione el botón para actualizar manualmente sin reiniciar - + Do not close settings until manual update is complete No cierre la ventana de configuración hasta que la actualización manual este completa. - + Download card pictures on the fly Descargar imagenes de las cartas rapidamente - + How to add a custom URL Cómo agregar una URL personalizada - + Delete Downloaded Images Eliminar imágenes descargadas - + Reset Download URLs Reiniciar descarga de URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckListModel - + Count - + Set - + Number Número - + Provider ID - + Card Carta @@ -1545,12 +1528,12 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags Editar etiquetas - + Rename Deck Renombrar mazo - + Save Deck to Clipboard - + Annotated Anotado - + Annotated (No set info) - + Not Annotated No Anotado - + Not Annotated (No set info) - + Rename File Renombrar archivo - + Delete File Eliminar archivo - + Set Banner Card - - + + New name: Nuevo nombre: - - + + Error Error - + Rename failed - + Delete file Eliminar archivo - + Are you sure you want to delete the selected file? ¿Estás seguro de que quieres eliminar el archivo seleccionado? - + Delete failed Borrado fallido @@ -1767,32 +1750,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Banquillo bloqueado - - + + Error Error - + The selected file could not be loaded. El fichero seleccionado no pudo cargarse. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice @@ -2374,17 +2357,17 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error Error - + Invalid deck list. Lista de mazo inválida. @@ -2885,17 +2868,17 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgLoadDeckFromClipboard - + Load deck from clipboard Cargar mazo del portapapeles - + Error Error - + Invalid deck list. Lista de mazo inválida. @@ -2903,43 +2886,43 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Cargar mazo + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Filtro no válido @@ -3153,12 +3169,12 @@ Tu correo electrónico se utilizará para verificar tu cuenta. DlgSettings - + Unknown Error loading card database Error desconocido al cargar la base de datos de cartas. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3175,7 +3191,7 @@ Podrías necesitar volver a ejecutar oracle para actualizar tu base de datos de ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database version is too old. This can cause problems loading card information or images @@ -3192,7 +3208,7 @@ Normalmente esto se soluciona volviendo a ejecutar oracle para actualizar tu bas ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3205,7 +3221,7 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues c ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - + File Error loading your card database. Would you like to change your database location setting? @@ -3214,7 +3230,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3223,7 +3239,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3236,59 +3252,59 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de mazos no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de imágenes de las cartas no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + Settings Preferencias - + General General - + Appearance Apariencia - + User Interface Interfaz de usuario - + Card Sources Origen de la carta - + Chat Chat - + Sound Sonido - + Shortcuts Atajos de teclado @@ -3602,67 +3618,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4110,143 +4126,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reiniciar todos los caminos - + All paths have been reset Todos los caminos se han reiniciado - - - - - - - + + + + + + + Choose path Elija ruta - + Personal settings Preferencias personales - + Language: Idioma: - + Paths (editing disabled in portable mode) Rutas (edición deshabilitada en modo portatil) - + Paths Rutas - + How to help with translations Cómo ayudar con las traducciones - + Decks directory: Directorio de mazos: - + Filters directory: Directorio de filtros: - + Replays directory: Directorio de repeticiones: - + Pictures directory: Directorio de imágenes: - + Card database: Base de datos de cartas: - + Custom database directory: Directorio de base de datos personalizado: - + Token database: Base de datos de tokens: - + Update channel Actualizar canal - + Check for client updates on startup Comprobar actualizaciones del cliente al arrancar - + Check for card database updates on startup Comprobar actualizaciones de la base de datos de cartas al arrancar - + Don't check No comprobar - + Prompt for update - + Always update in the background Siempre actualizar en segundo plano - + Check for card database updates every Comprobar actualizaciones de la base de datos de cartas cada - + days días - + Notify if a feature supported by the server is missing in my client Notificar si hace falta en mi cliente alguna característica soportada por el servidor - + Automatically run Oracle when running a new version of Cockatrice Oracle se ejecutará automáticamente cuando se ejecuté una nueva versión de Cockatrice - + Show tips on startup Mostrar tips al inicio - + Last update check on %1 (%2 days ago) @@ -4254,47 +4270,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Cementerio - + &View graveyard Ver &cementerio - + &Move graveyard to... &Mover cementerio a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &All players - + &Hand &Mano - + &Exile &Exilio - + Reveal random card to... Mostrar una carta al azar a... @@ -4302,88 +4318,88 @@ You may have to manually download the new version. HandMenu - + &Hand &Mano - + &View hand &Ver mano - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... &Mover mano a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &Graveyard &Cementerio - + &Exile &Exilio - + &Reveal hand to... &Mostrar mano a... - - + + All players - + Reveal r&andom card to... Mostrar una carta al azar a... @@ -4391,52 +4407,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck Crear nuevo mazo - + Browse Decks Buscar mazos - + Browse Card Database Buscar base de datos de cartas - + Browse EDHRec - + Browse Archidekt - + View Replays Ver repeticiones - + Quit Quitar - + Connecting... Conectando... - + Connect Conectar - + Play Jugar @@ -4444,193 +4460,213 @@ You may have to manually download the new version. LibraryMenu - + &Library &Biblioteca - + &View library &Ver biblioteca - + View &top cards of library... Ver cartas de la parte superior de la biblioteca - + View bottom cards of library... Ver cartas de la parte inferior de la biblioteca - + Reveal &library to... Mostrar &biblioteca a... - + Lend library to... - + Reveal &top cards to... Mostrar &cartas de la parte superior a... - + &Top of library... &Parte superior de la biblioteca... - + &Bottom of library... &Parte inferior de la biblioteca... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card &Robar carta - + D&raw cards... - + &Undo last draw - + Shuffle Barajar - + &Play top card &Jugar carta de la parte superior - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... Barajar cartas de la parte superior... - + &Draw bottom card &Robar carta de la parte inferior - + D&raw bottom cards... - + &Play bottom card &Jugar carta de la parte inferior - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top Poner carta de la parte inferior en la parte superior - + Shuffle bottom cards... Barajar cartas de la parte inferior... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4730,18 +4766,8 @@ Will now login. Iniciando sesión. - - Number of players - Número de jugadores - - - - Please enter the number of players. - Por favor, introduzca el número de jugadores. - - - - + + Player %1 Jugador %1 @@ -4844,8 +4870,8 @@ Iniciando sesión. - - + + Error Error @@ -5252,36 +5278,36 @@ La versión local es %1, la versión remota es %2. Mostrar/Ocultar - + New Version Nueva Versión - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Felicitaciones se ha actualizado Cockatrice %1! Oracle actualizará tu base de datos de cartas. - + Cockatrice installed Cockatrice está instalado. - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. ¡Felicitaciones! Se ha instalado Cockatrice %1 Ahora se ejecutará Oracle para instalar la base de datos de cartas inicial. - + Card database Base de datos de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5290,29 +5316,29 @@ If unsure or first time user, choose "Yes" Si no está seguro o es su primera vez, seleccione "Sí" - - + + Yes Si - - + + No No - + Open settings Abrir preferencias - + New sets found Nuevas ediciones encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5325,17 +5351,17 @@ Códigos de sets: %1 ¿Quieres habilitarlos? - + View sets Ver ediciones - + Welcome Bienvenido/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5344,64 +5370,64 @@ Todas las ediciones en la base de datos de cartas han sido habilitadas. Aprenda más sobre cambiar el orden de la edición o deshabilitando ediciones especificas y otras preferencias en la opción "Mantenedor de ediciones". - - + + Information Información - + A card database update is already running. La actualización de la base de datos de cartas ya está en marcha. - + Unable to run the card database updater: Imposible iniciar el actualizador de la base de datos de cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Ocurrió un error desconocido. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5411,55 +5437,55 @@ Esto no debería ser un problema, pero este mensaje podría significar que hay u Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - - - - - + + + + + Load sets/cards Cargar ediciones/cartas - + Selected file cannot be found. El archivo seleccionado no fue encontrado. - + You can only import XML databases at this time. Solo puedes importar bases de datos en XML en este momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Las nuevas ediciones/cartas se han agregado correctamente. Cockatrice está recargando la base de datos de cartas. - + Sets/cards failed to import. Las ediciones/cartas fallaron al ser importadas. - - - + + + Reset Password Reiniciar contraseña - + Your password has been reset successfully, you can now log in using the new credentials. Tu contraseña se ha reiniciado con éxito, ahora puedes conectarte con las nuevas credenciales. - + Failed to reset user account password, please contact the server operator to reset your password. No se pudo reiniciar la contraseña, por favor contacte con el operador del servidor para reiniciar su contraseña. - + Activation request received, please check your email for an activation token. Petición de activación recibida, por favor comprueba tu email para el código de activación. @@ -5510,7 +5536,7 @@ Cockatrice está recargando la base de datos de cartas. ManaBaseWidget - + Mana Base Base de maná @@ -5664,591 +5690,611 @@ Cockatrice está recargando la base de datos de cartas. MessageLogWidget - + from play del juego - + from their graveyard de su cementerio - + from exile del exilio - + from their hand de su mano - + the top card of %1's library la carta superior de la biblioteca de %1 - + the top card of their library la parte superior de su biblioteca - + from the top of %1's library desde la parte superior de la biblioteca de %1 - + from the top of their library Desde la parte superior de su biblioteca - + the bottom card of %1's library la carta de la parte inferior de la biblioteca de %1 - + the bottom card of their library La carta de la parte inferior de su biblioteca - + from the bottom of %1's library desde la parte inferior de la biblioteca de %1 - + from the bottom of their library Desde la parte inferior de su biblioteca - + from %1's library desde la biblioteca de %1 - + from their library desde su biblioteca - + from sideboard desde el banquillo - + from the stack desde la pila - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está manteniendo la carta superior %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 ya no mantiene revelada la carta superior %2. - + %1 can now look at top card %2 at any time. %1 ahora puede ver la carta superior de %2 en cualquier momento. - + %1 no longer can look at top card %2 at any time. %1 ya no puede ver la carta superior de %2 en cualquier momento. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %3's %4. - + %1 has conceded the game. %1 ha concedido la partida. - + %1 has unconceded the game. %1 ha concedido la partida - + %1 has restored connection to the game. %1 ha recuperado la conexión a la partida. - + %1 has lost connection to the game. %1 ha perdido la conexión a la partida. - + %1 points from their %2 to themselves. %1 apunta desde su %2 a si mismo. - + %1 points from their %2 to %3. %1 apunta desde su %2 a %3. - + %1 points from %2's %3 to themselves. %1 apunta desde el %3 de %2 a si mismo. - + %1 points from %2's %3 to %4. %1 apunta desde el %3 de %2 a %4. - + %1 points from their %2 to their %3. %1 apunta de su %2 a su %3. - + %1 points from their %2 to %3's %4. %1 apunta desde su %2 al %4 de %3. - + %1 points from %2's %3 to their own %4. %1 apunta desde el %3 de %2 a su %4. - + %1 points from %2's %3 to %4's %5. %1 apunta desde el %3 de %2 al %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 crea una ficha: %2%3. - + %1 has loaded a deck (%2). %1 ha cargado un mazo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha cargado un mazo con %2 cartas en el banquillo (%3). - + %1 destroys %2. %1 destruye %2. - + a card una carta - + %1 gives %2 control over %3. %1 entrega a %2 el control sobre %3. - + %1 puts %2 into play%3 face down. %1 pone %2 en juego %3 bocabajo. - + %1 puts %2 into play%3. %1 pone %2 en juego %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 pone %2%3 en su cementerio. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 exilia %2%3. - + %1 moves %2%3 to their hand. %1 mueve %2%3 a su mano. - + %1 puts %2%3 into their library. %1 pone %2%3 en su biblioteca. - + %1 puts %2%3 onto the bottom of their library. %1 pone %2%3 al fondo de su biblioteca. - + %1 puts %2%3 on top of their library. %1 pone %2%3 en la parte superior de su biblioteca. - + %1 puts %2%3 into their library %4 cards from the top. %1 pone %2%3 en la biblioteca de su propietario en el %4 lugar desde la parte superior - + %1 moves %2%3 to sideboard. %1 mueve %2%3 al banquillo. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 juega %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 roba %2 carta(s).%1 roba %2 c%1 roba %2 c - + %1 is looking at %2. %1 está mirando a %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom parte inferior - + top parte superior - + %1 turns %2 face-down. %1 pone %2 boca abajo. - + %1 turns %2 face-up. %1 pone %2 boca arriba. - + The game has been closed. La partida ha sido cerrada. - + The game has started. La partida ha comenzado. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 se ha unido a la partida. - + %1 is now watching the game. %1 está ahora observando la partida. - + You have been kicked out of the game. Has sido expulsado de la partida. - + %1 has left the game (%2). %1 ha dejado la partida (%2). - + %1 is not watching the game any more (%2). %1 ya no está viendo el juego (%2). - + %1 is not ready to start the game any more. %1 está preparado/a para empezar la partida. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 barajea su mazo y roba una nueva mano de %2 carta(s).%1 barajea su mazo y roba una nueva mano de %2 carta(s)%1 barajea su mazo y roba una nueva mano de %2 carta(s) - + %1 shuffles their deck and draws a new hand. %1 Baraja su maso y robas una nueva mano. - + You are watching a replay of game #%1. Estás viendo una repetición de la partida #%1. - + %1 is ready to start the game. %1 está preparado/a para empezar la partida. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta(s)%1 cars%1 cars - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela al azar %2%3 a %4. - + %1 randomly reveals %2%3. %1 revela al azar %2%3. - + %1 peeks at face down card #%2. %1 está mirando la carta #%2 boca abajo. - + %1 peeks at face down card #%2: %3. %1 mira la carta boca abajo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 Orden del turno invertido, ahora es %2. - + reversed Invertido. - + normal Normal. - + Heads Cara - + Tails Cruz - + %1 flipped a coin. It landed as %2. %1 tira una moneda. Ha salido %2. - + %1 rolls a %2 with a %3-sided die. %1 sacó un %2 con un dado de %3 caras. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. Turno %1. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 establece el contador %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 establece que %2 no se endereze normalmente. - + %1 sets %2 to untap normally. %1 establece que %2 se endereze normalmente. - + %1 removes the PT of %2. %1 elimina la F/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 cambia la F/R de %2 de nada a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia la F/R de %2 de %3 a %4. - + %1 has locked their sideboard. %1 ha bloqueado su banquillo. - + %1 has unlocked their sideboard. %1 ha desbloqueado su banquillo. - + %1 taps their permanents. %1 gira sus permanentes. - + %1 untaps their permanents. %1 ha enderezado sus permanentes. - + %1 taps %2. %1 gira %2. - + %1 untaps %2. %1 endereza %2. - + %1 shuffles %2. %1 baraja %2. - + %1 shuffles the bottom %3 cards of %2. %1 barajea las últimas %3 cartas de %2. - + %1 shuffles the top %3 cards of %2. %1 barajea las primeras %3 cartas de %2. - + %1 shuffles cards %3 - %4 of %2. %1 barajea las cartas %3 - %4 de %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 deshace su último robo. - + %1 undoes their last draw (%2). %1 deshace su último robo (%2). @@ -6256,110 +6302,110 @@ Cockatrice está recargando la base de datos de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palabra1 Palabra2 Palabra3 - + Add New Message Añadir nuevo mensaje - + Edit Message Editar mensaje - + Remove Message Borrar mensaje - + Add message Añadir mensaje - - + + Message: Mensaje: - + Edit message Editar mensaje - + Chat settings Preferencias de Chat - + Custom alert words Palabras de alerta personalizadas - + Enable chat mentions Habilitar las menciones en el chat - + Enable mention completer Habilitar completado de menciones - + In-game message macros Macros para mensajes durante la partida - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensajes de sala de usuarios sin registrar - + Ignore private messages sent by unregistered users Ignorar mensajes privados de usuarios sin registrar - - + + Invert text color Invertir el color del texto - + Enable desktop notifications for private messages Habilitar notificaciones de escritorio para los mensajes privados - + Enable desktop notification for mentions Habilitar notificaciones de escritorio para menciones - + Enable room message history on join Habilitar historial de mensajes de sala al unirse - - + + (Color is hexadecimal) (El color es hexadecimal) - + Separate words with a space, alphanumeric characters only Separa las palabras con un espacio, solo caracteres alfanuméricos @@ -6372,32 +6418,37 @@ Cockatrice está recargando la base de datos de cartas. Mover a - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand &Mano - + &Graveyard &Cementerio - + &Exile &Exilio @@ -6539,57 +6590,57 @@ Cockatrice está recargando la base de datos de cartas. PhasesToolbar - + Untap step Paso de enderezar - + Upkeep step Paso de mantenimiento - + Draw step Paso de robar - + First main phase Primera fase principal - + Beginning of combat step Paso de inicio del combate - + Declare attackers step Paso de declarar atacantes - + Declare blockers step Paso de declarar bloqueadores - + Combat damage step Paso de daño de combate - + End of combat step Paso de final del combate - + Second main phase Segunda fase principal - + End of turn step Paso final @@ -6606,134 +6657,138 @@ Cockatrice está recargando la base de datos de cartas. PlayerActions - + View top cards of library Ver cartas de la parte superior de la biblioteca - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Número de cartas: (máx. %1) - + View bottom cards of library Ver cartas de la parte inferior de la biblioteca - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand Robar mano - + 0 and lower are in comparison to current hand size - + Draw cards Robar cartas + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens Crear fichas - - + + Number: Número: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness Cambiar fuerza/resistencia - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters Establecer contadores @@ -6741,48 +6796,65 @@ Cockatrice está recargando la base de datos de cartas. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6831,17 +6903,25 @@ Cockatrice está recargando la base de datos de cartas. Fecha de lanzamiento - - + + Descending Descendente - + Ascending Ascendente + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6980,6 +7060,33 @@ Cockatrice está recargando la base de datos de cartas. A .cod version of this deck already exists. Overwrite it? Ya existe una versión .cod de este mazo. Sobrescribirla? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7128,37 +7235,37 @@ Cockatrice está recargando la base de datos de cartas. RfgMenu - + &Exile &Exilio - + &View exile Ver &exilio - + &Move exile to... &Mover exilio a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &Hand &Mano - + &Graveyard &Cementerio @@ -7201,6 +7308,14 @@ Cockatrice está recargando la base de datos de cartas. Partidas + + SayMenu + + + S&ay + + + SequenceEdit @@ -7265,53 +7380,53 @@ Cockatrice está recargando la base de datos de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar atajos por defecto - + Do you really want to restore all default shortcuts? ¿Realmente quieres restablecer los atajos predeterminados? - + Clear all default shortcuts Limpiar todos los atajos predeterminados - + Do you really want to clear all shortcuts? ¿Realmente quieres limpiar los atajos predeterminados? - + Section: Sección: - + Action: Acción: - + Shortcut: Atajo: - + How to set custom shortcuts Cómo fijar atajos personalizados - + Clear all shortcuts - + Search by shortcut name @@ -7379,27 +7494,27 @@ Por favor, revise su configuración de atajos. SoundSettingsPage - + Enable &sounds Activar &sonidos - + Current sounds theme: Tema de sonidos actual: - + Test system sound engine Probar el audio del sistema - + Sound settings Preferencias de sonido - + Master volume Volumen maestro @@ -7615,59 +7730,123 @@ Por favor, revise su configuración de atajos. TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7675,60 +7854,52 @@ Por favor, revise su configuración de atajos. TabDeckEditor - + Card Info Información de la carta - + Deck Mazo - + Filters Filtros - + &View &Ver - + Card Database - + Printing - - - - - + Visible Visible - - - - - + Floating Flotante - + Reset layout Reinicializar la disposición - + Deck: %1 Mazo: %1 @@ -7736,61 +7907,55 @@ Por favor, revise su configuración de atajos. TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info Información de la carta - - + + Deck Mazo - - + + Filters - + &View - + Printing - - - - + Visible Visible - - - - + Floating - + Reset layout @@ -7855,7 +8020,7 @@ Por favor, revise su configuración de atajos. - + New folder Nueva carpeta @@ -7937,18 +8102,18 @@ Por favor, introduce un nombre: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Nombre de la nueva carpeta: @@ -7966,12 +8131,12 @@ Por favor, introduce un nombre: - + Error Error - + Could not open deck at %1 @@ -8020,197 +8185,191 @@ Por favor, introduce un nombre: TabGame - - - + + + Replay Repetir - - + + Game Partida - - + + Player List Lista de jugadores - - + + Card Info Información de la carta - - + + Messages Mensajes - - + + Replay Timeline Historial de partidas - + &Phases &Fases - + &Game &Partida - + Next &phase Próxima &fase - + Next phase with &action Próxima fase con &acción - + Next &turn Próximo &turno - + Reverse turn order Orden del turno invertido. - + &Remove all local arrows &Retirar todas las flechas locales - + Rotate View Cl&ockwise Girar en sentido horario - + Rotate View Co&unterclockwise Girar en sentido antih&orario (&U) - + Game &information &Información de la partida - + Un&concede - - - + + + &Concede &Conceder - + &Leave game &Abandonar la partida - + C&lose replay &Cerrar repetición - + &Focus Chat &Resaltar chat - + &Say: &Decir: - + Selected cards Cartas seleccionadas - + &View &Ver - - - - + Visible Visible - - - - + Floating Flotante - + Reset layout Resetear la distribución - + Concede Conceder - + Are you sure you want to concede this game? ¿Estás seguro de que quieres conceder esta partida? - + Unconcede Revertir rendición - + You have already conceded. Do you want to return to this game? Ya te rendiste. ¿Quieres regresar a este juego? - + Leave game Abandonar la partida - + Are you sure you want to leave this game? ¿Estás seguro de que quieres abandonar la partida? - + A player has joined game #%1 Un jugador se ha unido a la partida #%1 - + %1 has joined the game %1 se ha unido a la partida - + You have been kicked out of the game. Has sido expulsado de la partida. @@ -9309,142 +9468,152 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserInterfaceSettingsPage - + General interface settings Preferencias generales de la interfaz - + &Double-click cards to play them (instead of single-click) &Doble click en las cartas para jugarlas (en lugar de un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jugar todas las cartas que no sean tierras en la pila (en lugar de en el campo de batalla) por defecto. - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Anotar en las fichas. - - - Use tear-off menus, allowing right click menus to persist on screen - Usar menus flotantes, permitir click derecho para anclar menus en la pantalla - - - - Notifications settings - Ajustes de notificaciones - - - - Enable notifications in taskbar - Habilitar notificaciones en la barra de tareas - - - - Notify in the taskbar for game events while you are spectating - Notificar en la barra de tareas los eventos de la partida mientras estás como espectador - - - - Notify in the taskbar when users in your buddy list connect - Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - - - - Animation settings - Preferencias de animación - - - - &Tap/untap animation - Animación de &girar/enderezar - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + Usar menus flotantes, permitir click derecho para anclar menus en la pantalla + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ajustes de notificaciones + + + + Enable notifications in taskbar + Habilitar notificaciones en la barra de tareas - do nothing - no hacer nada + Notify in the taskbar for game events while you are spectating + Notificar en la barra de tareas los eventos de la partida mientras estás como espectador + + + + Notify in the taskbar when users in your buddy list connect + Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - ask to convert to .cod - + Animation settings + Preferencias de animación + + + + &Tap/untap animation + Animación de &girar/enderezar - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + no hacer nada + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9508,23 +9677,24 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9551,25 +9721,108 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9577,22 +9830,32 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9628,7 +9891,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9636,19 +9899,19 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9656,27 +9919,37 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9690,52 +9963,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - + Visual - + Loading database ... - + Clear all filters Limpiar todos los filtros - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9743,56 +9986,64 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Robar una nueva mano de muestra - + Sample hand size @@ -9800,17 +10051,17 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9826,47 +10077,52 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None Ninguno - + Filepath @@ -9967,133 +10223,133 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas WndSets - + Move selected set to the top Mover el set seleccionado al principio - + Move selected set up Mover arriba el set seleccionado - + Move selected set down Mover abajo el set seleccionado - + Move selected set to the bottom Mover el set seleccionado al final - + Search by set name, code, or type Buscar por nombre de edición, código, o tipo - + Default order Orden por defecto - + Restore original art priority order Restaurar orden predeterminado de arte original - + Enable all sets Seleccionar todos los sets - + Disable all sets Deseleccionar todos los sets - + Enable selected set(s) Habilitar set(s) seleccionados - + Disable selected set(s) Deshabilitar set(s) seleccionados - + Deck Editor Editor de mazo - + Use CTRL+A to select all sets in the view. Usa CTRL+A para seleccionar todas las colecciones aquí mostradas. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo las cartas en las colecciones permitidas aparecerán en la lista de cartas del editor de mazos. - + Image priority is decided in the following order: La prioridad de las imágenes es decidida en el siguiente orden: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primero la Carpeta PERSONALIZADA (%1), luego las Ediciones Habilitadas en este mensaje (de Arriba a Abajo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte de carta - + How to use custom card art Cómo usar arte de carta personalizada. - + Hints Consejos - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Ordenar por columna te permite encontrar una edición sin cambiar la prioridad de edición. - + To enable ordering again, click the column header until this message disappears. Para habilitar el orden de nuevo, haga click en el encabezado de la columna hasta que este mensaje deje de aparecer. - + Use the current sorting as the set priority instead Usa el filtro actual como la prioridad de edición - + Sorts the set priority using the same column Ordena la prioridad de edición usando la misma columna - + Manage sets Mantenedor de ediciones @@ -10101,72 +10357,72 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Desagrupado - + Group by Type Agrupar por tipo - + Group by Mana Value Agrupar por valor de maná - + Group by Color Agrupar por color - + Unsorted Desordenado - + Sort by Name Ordenar por nombre - + Sort by Type Ordenar por tipo - + Sort by Mana Cost Ordenar por coste de maná - + Sort by Colors Ordenar por colores - + Sort by P/T - + Sort by Set - + shuffle when closing barajar al cerrar - + pile view vista de la pila @@ -10201,7 +10457,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - + Deck Editor Editor de mazos @@ -10282,7 +10538,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - + Replays Repeticiones @@ -10434,7 +10690,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - + Reset Layout Restablecer disposición @@ -10855,8 +11111,9 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - Toggle Untap - Alternar enderezamiento + Toggle Skip Untapping + Toggle Untap + @@ -10875,98 +11132,102 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas + Play Card, Face Down + + + + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Clonar carta - + Create Token... Crear ficha... - + Create All Related Tokens Crear todas las fichas relacionadas - + Create Another Token Crear otra ficha - + Set Annotation... Escribir anotación... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library Parte inferior de la biblioteca - + - - + + Exile Exilio - + - + Graveyard Cementerio - + Hand Mano - - + + Top of Library Parte superior de la biblioteca - - + Battlefield, Face Down Campo de batalla, boca abajo @@ -11002,234 +11263,246 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas - + Stack Apilar - + Graveyard (Multiple) Cementerio (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Exiliar (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card Robar carta de la parte inferior - + Draw Multiple Cards from Bottom... Robar múltiples cartas de la parte inferior... - + Draw Arrow... Dibujar flecha - + Remove Local Arrows Eliminar flechas - + Leave Game Abandonar partida - + Concede Conceder - + Roll Dice... Lanzar dado... - + Shuffle Library Barajar biblioteca - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Robar una carta - + Draw Multiple Cards... Robar múltiples cartas... - + Undo Draw Deshacer último robo - + Always Reveal Top Card Mostrar siempre la carta superior - + Always Look At Top Card Mirar siempre la carta superior - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Rotar vista en sentido horario - + Rotate View Counterclockwise Rotar vista en sentido antihorario - + Unfocus Text Box No resaltar la caja de texto - + Focus Chat Resaltar el chat - + Clear Chat Borrar chat - + Refresh Actualizar - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home Inicio - + Visual Deck Storage - + Deck Storage Almacenamiento de mazos - + Server Servidor - + Account Cuenta - + Administration Administración - + Logs Historial diff --git a/cockatrice/translations/cockatrice_fi.ts b/cockatrice/translations/cockatrice_fi.ts index 16f94f1c0..1202e1f1e 100644 --- a/cockatrice/translations/cockatrice_fi.ts +++ b/cockatrice/translations/cockatrice_fi.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Teeman asetukset - + Current theme: Nykyinen teema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Kortin renderöinti - + Display card names on cards having a picture Näytä kortin nimi korteissa, joissa on kuva - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Suurenna kortti kun hiiri on kortin päällä - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Käden layout - + Display hand horizontally (wastes space) Näytä käsikortit vierekkäin vaakatasossa (vie enemmän tilaa) - + Enable left justification Järjestä vasemmalta - + Table grid layout Pöydän grid-asetelma - + Invert vertical coordinate Käänteiset pystykoordinaatit - + Minimum player count for multi-column layout: Pelaajien vähimmäismäärä moniosaisessa layoutissa: - + Maximum font size for information displayed on cards: Suurin fonttikoko korteissa näkyville infoteksteille: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorSettingsPage - - + + Update Spoilers Päivitä spoilerit - - + + Success Onnistui - + Download URLs have been reset. Latauksen URL:t on resetoitu - + Downloaded card pictures have been reset. Ladattujen korttien kuvat on resetoitu. - + Error Virhe - + One or more downloaded card pictures could not be cleared. Ei voitu poistaa yhtä tai useampaa kortin kuvaa - + Add URL Lisää URL - - + + URL: URL: - - + + Edit URL Muokkaa URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Päivitetään... - + Choose path Valitse polku - + URL Download Priority URL Latausprioriteetti - + Spoilers Spoilerit - + Download Spoilers Automatically Lataa spoilerit automaattisesti - + Spoiler Location: Spoilerien sijainti: - + Last Change Muokattu viimeksi - + Spoilers download automatically on launch Lataa spoilerit latauksen yhteydessä - + Press the button to manually update without relaunching Lataa spoilerit manuaalisesti ilman uudelleenkäynnistystä - + Do not close settings until manual update is complete Älä sulje asetuksia, ennen kuin manuaalinen päivitys on valmis - + Download card pictures on the fly Lataa korttikuvat kun niitä tarvitaan - + How to add a custom URL Miten lisään oman URL:n - + Delete Downloaded Images Poista ladatut kuvat - + Reset Download URLs Resetoi latausten URL:t - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckListModel - + Count - + Set - + Number Numero - + Provider ID - + Card Kortti @@ -1545,12 +1528,12 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. Sideboard locked - - + + Error Virhe - + The selected file could not be loaded. Valittua tiedostoa ei voi avata. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2885,17 +2868,17 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgLoadDeckFromClipboard - + Load deck from clipboard Avaa pakka leikepöydältä - + Error Virhe - + Invalid deck list. Pakka ei ole kelvollinen. @@ -2903,43 +2886,43 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Lataa pakka + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3152,12 +3168,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Tuntematon virhe ladattaessa korttitietokantaa - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3174,7 +3190,7 @@ Voit joutua ajamaan Oraclen ja päivittämään korttitietokannan uudelleen Haluatko vaihtaa tietokantasi sijaintia? - + Your card database version is too old. This can cause problems loading card information or images @@ -3191,7 +3207,7 @@ Yleensä tämä voidaan korjata palaamalla oracleen päivittämään korttitieto Haluatko vaihtaa tietokantasi sijaintia? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3200,7 +3216,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3209,7 +3225,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3218,7 +3234,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3227,59 +3243,59 @@ Would you like to change your database location setting? - - - + + + Error Virhe - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Pakkojen tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin pakat -kansiolle? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Kuvien tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin kuvat -kansiolle? - + Settings Asetukset - + General Yleinen - + Appearance Ulkonäkö - + User Interface Käyttöliittymä - + Card Sources Korttien lähteet - + Chat Chat - + Sound Äänet - + Shortcuts Pikanäppäimet @@ -3593,67 +3609,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4101,143 +4117,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Valitse polku - + Personal settings Henkilökohtaiset asetukset - + Language: Kieli: - + Paths (editing disabled in portable mode) Polut (editoiminen poistettu käytöstä kannettavassa versiossa) - + Paths Polut - + How to help with translations - + Decks directory: Pakkojen kansio: - + Filters directory: - + Replays directory: Replay kansio: - + Pictures directory: Kuvakansio: - + Card database: Korttitietokanta: - + Custom database directory: - + Token database: Tokenitietokanta: - + Update channel Päivitykset - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Ilmoita jos ohjelmastani puuttuu jokin serverin tarjoama ominaisuus - + Automatically run Oracle when running a new version of Cockatrice Suorita Oracle automaattisesti kun Cockatricen uusi versio on asennettu - + Show tips on startup Näytä vinkit käynnistyksen yhteydessä - + Last update check on %1 (%2 days ago) @@ -4245,47 +4261,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4293,88 +4309,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4382,52 +4398,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4435,193 +4451,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4719,18 +4755,8 @@ Will now login. Kirjautuu. - - Number of players - Pelaajien lukumäärä - - - - Please enter the number of players. - Anna pelaajien lukumäärä. - - - - + + Player %1 Pelaaja %1 @@ -4833,8 +4859,8 @@ Kirjautuu. - - + + Error Virhe @@ -5241,36 +5267,36 @@ Paikallinen versio on %1, etäversio on %2. - + New Version Uusi versio - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice asennettu - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Korttitietokanta - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5279,46 +5305,46 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Kyllä - - + + No Ei - + Open settings Avaa asetukset - + New sets found Löytyi uusia settejä! - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Näytä setit - + Welcome Tervetuloa - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5327,64 +5353,64 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Tietoa - + A card database update is already running. Korttitietokannan päivitys on jo käynnissä - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5395,55 +5421,55 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Lataa setit/kortit - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Salasanasi on muutettu, voit nyt kirjautua sisään uudella salasanallasi. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. @@ -5494,7 +5520,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5648,590 +5674,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play pelistä - + from their graveyard Graveyardilta - + from exile exilestä - + from their hand kädestään - + the top card of %1's library %1n pakan päällimmäinen kortti - + the top card of their library pakan päällimmäisen kortin - + from the top of %1's library %1n pakan päältä - + from the top of their library Pakkansa päältä - + the bottom card of %1's library %1n pakan alimmainen kortti - + the bottom card of their library pakan alimmaisen kortin - + from the bottom of %1's library %1n pakan pohjalta - + from the bottom of their library pakan alta - + from %1's library %1n pakasta - + from their library pakasta - + from sideboard sideboardilta - + from the stack stäkistä - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 pelaa pakan päällimmäinen kortti %2 esillä. - + %1 is not revealing the top card %2 any longer. %1 ei pelaa enää pakan päällimmäinen kortti %2 esillä, - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 luovutti. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 on jälleen yhteydessä peliin. - + %1 has lost connection to the game. %1 menetti yhteyden peliin. - + %1 points from their %2 to themselves. %1 osoittaa kortista %2 itseensä. - + %1 points from their %2 to %3. %1 osoittaa kortista %2 korttiin %3. - + %1 points from %2's %3 to themselves. %1 osoittaa %2n kortista %3 itseensä. - + %1 points from %2's %3 to %4. %1 osoittaa %2n kortista %3 korttiin %4 - + %1 points from their %2 to their %3. %1 osoittaa kortistaan %2 korttiin %3. - + %1 points from their %2 to %3's %4. %1 osoittaa kortistaan %2 pelaajan %3 korttiin %4. - + %1 points from %2's %3 to their own %4. %1 osoittaa pelaajan %2 kortista %3 korttiin %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 tekee tokenin: %2%3. - + %1 has loaded a deck (%2). %1 on ladannut pakan (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 on ladannut pakan, jossa on %2 sideboard korttia (%3) - + %1 destroys %2. %1 tuhoaa kortin %2. - + a card kortti - + %1 gives %2 control over %3. %1 antaa pelaajalle %2 kontrollin korttiin %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 laittaa kortin %2 peliin%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 laittaa kortin %2%3 graveyardille + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 laittaa exileen kortin %2%3. - + %1 moves %2%3 to their hand. %1 laittaa kortin %2%3 käteen. - + %1 puts %2%3 into their library. %1 laittaa kortin %2%3 pakkaan. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 laittaa kortin %2%3 pakan päälle. - + %1 puts %2%3 into their library %4 cards from the top. %1 laittaa kortin %2%3 pakkaansa %4 kortiksi päältä lukien. - + %1 moves %2%3 to sideboard. %1 siirtää kortin %2%3 sideboardille. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 pelaa kortin %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. Peli on suljettu. - + The game has started. Peli alkaa. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 on liittynyt peliin. - + %1 is now watching the game. %1 seuraa peliä. - + You have been kicked out of the game. Sinut on kickattu pois pelistä. - + %1 has left the game (%2). %1 on lähtenyt pelistä. (%2). - + %1 is not watching the game any more (%2). %1 ei enää seuraa peliä (%2) - + %1 is not ready to start the game any more. %1 ei ole enää valmis aloittamaan peliä. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 sekoittaa pakkansa ja nostaa uuden käden. - + You are watching a replay of game #%1. Katsot pelin #%1 replayta. - + %1 is ready to start the game. %1 on valmis aloittamaan pelin. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 käänsi vuorojärjestyksen; nyt se on %2. - + reversed käänteinen - + normal normaali - + Heads Kruuna - + Tails Klaava - + %1 flipped a coin. It landed as %2. %1 heitti kolikkoa ja tulokseksi tuli %2. - + %1 rolls a %2 with a %3-sided die. %1 heitti tuloksen %2. %3 -sivuisella nopalla - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1n vuoro. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 asettaa countterin %2 arvoon %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 on lukinnut sideboardinsa. - + %1 has unlocked their sideboard. %1 on avannut sideboardinsa. - + %1 taps their permanents. %1 täppää kaikki permanentit. - + %1 untaps their permanents. %1 untäppää kaikki permanentit. - + %1 taps %2. %1 täppää kortin %2. - + %1 untaps %2. %1 untäppää kortin %2. - + %1 shuffles %2. %1 sekoittaa %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 irrottaa kortin %2. - + %1 undoes their last draw. %1 peruuttaa viimeisimmän kortinnostonsa. - + %1 undoes their last draw (%2). %1 peruuttaa viimeisimmän kortinnostonsa. (%2). @@ -6239,110 +6285,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lisää viesti - - + + Message: Viesti: - + Edit message Edit message - + Chat settings Keskusteluasetukset - + Custom alert words Custom alert words - + Enable chat mentions Salli keskustelumaininnat - + Enable mention completer Enable mention completer - + In-game message macros Pelinsisäiset viestimakrot - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Käänteinen tekstin väri - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Väri on heksadesimaaliluku) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -6355,32 +6401,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6522,57 +6573,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Untap-kohta - + Upkeep step Upkeep-kohta - + Draw step Nostokohta - + First main phase First main phase - + Beginning of combat step Beginning of combat step - + Declare attackers step Declare attackers step - + Declare blockers step Declare blockers step - + Combat damage step Combat damage step - + End of combat step End of combat step - + Second main phase Second main phase - + End of turn step End of turn step @@ -6589,134 +6640,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6724,48 +6779,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6814,17 +6886,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6963,6 +7043,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7111,37 +7218,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7184,6 +7291,14 @@ Cockatrice will now reload the card database. Games + + SayMenu + + + S&ay + + + SequenceEdit @@ -7248,53 +7363,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7363,27 +7478,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -7599,59 +7714,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7659,60 +7838,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + Card Database - + Printing - - - - - + Visible Visible - - - - - + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -7720,61 +7891,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7839,7 +8004,7 @@ Please check your shortcut settings! - + New folder New folder @@ -7921,18 +8086,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Name of new folder: @@ -7950,12 +8115,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -8004,197 +8169,191 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Käännä vuorojärjestys - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards - + &View &View - - - - + Visible Visible - - - - + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Poistu pelistä - + Are you sure you want to leave this game? Haluatko varmasti poistua pelistä? - + A player has joined game #%1 Pelaaja on littynyt peliin #%1 - + %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -9296,142 +9455,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - Use tear-off menus, allowing right click menus to persist on screen - - - - Notifications settings - Ilmoitusasetukset - - - - Enable notifications in taskbar - Ota tehtäväpalkin ilmoitukset käyttöön - - - - Notify in the taskbar for game events while you are spectating - Notify in the taskbar for game events while you are spectating - - - - Notify in the taskbar when users in your buddy list connect - Notify in the taskbar when users in your buddy list connect - - - - Animation settings - Animaatioasetukset - - - - &Tap/untap animation - &Tap/untap animation - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + Use tear-off menus, allowing right click menus to persist on screen + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ilmoitusasetukset + + + + Enable notifications in taskbar + Ota tehtäväpalkin ilmoitukset käyttöön - do nothing - + Notify in the taskbar for game events while you are spectating + Notify in the taskbar for game events while you are spectating + + + + Notify in the taskbar when users in your buddy list connect + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animaatioasetukset + + + + &Tap/untap animation + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9495,23 +9664,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9538,25 +9708,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9564,22 +9817,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9615,7 +9878,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9623,19 +9886,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9643,27 +9906,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9677,52 +9950,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9730,56 +9973,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9787,17 +10038,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9813,47 +10064,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9954,133 +10210,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -10088,72 +10344,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view @@ -10188,7 +10444,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor Deck Editor @@ -10269,7 +10525,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10421,7 +10677,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout Reset Layout @@ -10842,8 +11098,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - Toggle Untap + Toggle Skip Untapping + Toggle Untap + @@ -10862,98 +11119,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + + + + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library Bottom of Library - + - - + + Exile Exile - + - + Graveyard Graveyard - + Hand Hand - - + + Top of Library Top of Library - - + Battlefield, Face Down Battlefield, Face Down @@ -10989,234 +11250,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack Stack - + Graveyard (Multiple) Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index d01e564c0..f50d3afd6 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Rafraîchir - + Parse Set Name and Number (if available) Analyser le nom et le numéro de l'extension (si disponibles) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Ouvrir dans un nouvel onglet - + Are you sure? Êtes-vous sûr ? - + The decklist has been modified. Do you want to save the changes? La liste de deck a été modifiée. Voulez-vous enregistrer les modifications ? - - - - - - + + + + + + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 - + Could not save remote deck N'a pas pu sauvegarder le deck distant - - + + The deck could not be saved. Please check that the directory is writable and try again. Le deck n'a pas pu être enregistré. Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. - + Save deck Sauvegarder le deck - + The deck could not be saved. Le deck n'a pas pu être enregistré. - + There are no cards in your deck to be exported Il n'y a pas de cartes dans le deck à exporter @@ -133,202 +133,168 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. AppearanceSettingsPage - + seconds secondes - + Error Erreur - + Could not create themes directory at '%1'. N'a pas pu créer le dossier Thèmes à '%1' - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - L'activation de cette fonction désactivera l'utilisation du sélecteur d'impression. - -Vous ne pourrez plus gérer les préférences d'impression pour chaque deck ni voir les impressions sélectionnées par d'autres joueurs. - -Vous devrez utiliser le Gestionnaire d'extensions, accessible via Base de données de cartes -> Gérer les extensions. - -Êtes-vous sûr de vouloir activer cette fonction ? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Désactiver cette fonction activera le sélecteur d'impression. - -Vous pouvez désormais choisir les impressions pour chaque deck dans l'éditeur de deck et configurer l'impression ajoutée par défaut en l'épinglant dans le sélecteur d'impression. - -Vous pouvez également utiliser le gestionnaire de d'extension pour personnaliser l'ordre de tri des impressions dans le sélecteur d'impression (d'autres ordres de tri, comme l'ordre alphabétique ou la date de parution, sont disponibles). - -Êtes-vous sûr de vouloir désactiver cette fonction ? - - - - Confirm Change - Confirmer le changement - - - + Theme settings Paramètres du thème - + Current theme: Thème actuel : - + Open themes folder Ouvrir le dossier des thèmes - + Home tab background source: Source d'arrière-plan de l'onglet Accueil: - + Home tab background shuffle frequency: Fréquence de mélange de l'arrière-plan de l'onglet Accueil: - + Disabled Désactivé - + + Display card name of background in bottom right: + + + + Menu settings Options du Menu - + Show keyboard shortcuts in right-click menus Montrer les raccourcis clavier dans les menus clic-droit - + Show game filter toolbar above list in room tab - + Card rendering Rendu des cartes - + Display card names on cards having a picture Afficher les noms des cartes ayant une image - + Auto-Rotate cards with sideways layout Rotation automatique des cartes avec disposition horizontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) Remplacer toutes les illustrations de cartes par les préférences personnelles (comportement avant modification du ProviderID) - + Bump sets that the deck contains cards from to the top in the printing selector Mettre en avant que ce deck contient des cartes depuis le sommet, dans le sélecteur d'impression - + Scale cards on mouse over Agrandir les cartes lors du survol du curseur - + Use rounded card corners Utiliser des bords de cartes arrondis - + Minimum overlap percentage of cards on the stack and in vertical hand Pourcentage minimum de chevauchement des cartes dans la pile et dans la main verticale - + Maximum initial height for card view window: Hauteur initiale maximale pour la fenêtre d'affichage des cartes : - - + + rows lignes - + Maximum expanded height for card view window: Hauteur étendue maximale pour la fenêtre d'affichage des cartes : - + Card counters Marqueurs sur la carte - + Counter %1 Marqueur %1 - + Hand layout Disposition de la main - + Display hand horizontally (wastes space) Afficher la main horizontalement (perte d'espace) - + Enable left justification Activer la justification à gauche - + Table grid layout Disposition en forme de grille - + Invert vertical coordinate Inverser la disposition du champ de bataille - + Minimum player count for multi-column layout: Nombre minimum de joueurs pour la disposition multi-colonnes : - + Maximum font size for information displayed on cards: Taille maximale de la police pour les informations affichées sur les cartes  : @@ -336,7 +302,12 @@ Vous pouvez également utiliser le gestionnaire de d'extension pour personn ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -656,22 +627,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoPictureWidget - + View related cards Voir les cartes associées - + Add card to deck Ajouter la carte au deck - + Mainboard Deck principal - + Sideboard Réserve @@ -707,124 +678,124 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardMenu - + Re&veal to... Révéler à... - + &All players &Tous les joueurs - + View related cards Voir les cartes associées - + Token: Jeton : - + All tokens Tout les jetons - + &Select All &Sélectionner Tout - + S&elect Row Sél&ectionne une ligne - + S&elect Column Sél&ectionne une colonne - + &Play &Jouer - + &Hide &Cacher - + Play &Face Down Jouer &Face Cachée - + &Tap / Untap Turn sideways or back again &Engager / Dégager - - Toggle &normal untapping - Activer dégagement &normal + + Skip &untapping + - + T&urn Over Turn face up/face down Reto&urner - + &Peek at card face &Regarder furtivement la carte face cachée - + &Clone &Cloner - + Attac&h to card... Attac&her à une carte... - + Unattac&h Détac&her - + &Draw arrow... &Tracer une flèche... - + &Set annotation... &Annoter... - + Ca&rd counters Ma&rqueurs sur la carte - + &Add counter (%1) &Ajouter un marqueur (%1) - + &Remove counter (%1) &Retirer un marqueur (%1) - + &Set counters (%1)... &Changer le nombre de marqueurs (%1)... @@ -840,133 +811,133 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardZoneLogic - + their hand nominative sa main - + %1's hand nominative main de %1 - + their library look at zone sa bibliothèque - + %1's library look at zone bibliothèque de %1 - + of their library top cards of zone, de sa bibliothèque - + of %1's library top cards of zone de la bibliothèque de %1 - + their library reveal zone sa bibliothèque - + %1's library reveal zone bibliothèque de %1 - + their library shuffle sa bibliothèque - + %1's library shuffle bibliothèque de %1 - - - their library - nominative - sa bibliothèque - - - - %1's library - nominative - bibliothèque de %1 - + their library + nominative + sa bibliothèque + + + + %1's library + nominative + bibliothèque de %1 + + + their graveyard nominative son cimetière - + %1's graveyard nominative le cimetière de %1 - + their exile nominative sa zone d'exil - + %1's exile nominative la zone d'exil de %1 - - - their sideboard - look at zone - sa réserve - - - - %1's sideboard - look at zone - la réserve de %1 - their sideboard - nominative + look at zone sa réserve %1's sideboard + look at zone + la réserve de %1 + + + + their sideboard + nominative + sa réserve + + + + %1's sideboard nominative la réserve de %1 - + their custom zone '%1' nominative sa zone personnalisée '%1' - + %1's custom zone '%2' nominative La zone personnalisée '%2' de %1 @@ -1028,7 +999,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorCardDatabaseDockWidget - + Card Database @@ -1036,7 +1007,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorCardInfoDockWidget - + Card Info Infos de la carte @@ -1059,32 +1030,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Ajouter à la réserve - + Select Printing Choisir l'impression - + Show on EDHRec (Commander) Afficher sur EDHRec (Commandant) - + Show on EDHRec (Card) Afficher sur EDHRec (carte) - + Show Related cards Afficher les cartes associées - + Add card to &maindeck Ajouter la carte au &deck - + Add card to &sideboard Ajouter carte à la ré&serve @@ -1092,32 +1063,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorDeckDockWidget - + Loading Database... - + Banner Card Carte bannière - + Main Type Type principal - + Mana Cost Coût de mana - + Colors Couleurs - + Select Printing Choisir l'impression @@ -1190,17 +1161,17 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorFilterDockWidget - + Filters Filtres - + &Clear all filters &Effacer tous les filtres - + Delete selected Enlever la sélection @@ -1323,7 +1294,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorPrintingSelectorDockWidget - + Printing Selector Sélection d'impression @@ -1331,166 +1302,166 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorSettingsPage - - + + Update Spoilers Mettre à jour les spoilers - - + + Success Réussite - + Download URLs have been reset. Les URLs de téléchargement ont été réinitialisées. - + Downloaded card pictures have been reset. Les images des cartes téléchargées ont été supprimées. - + Error Erreur - + One or more downloaded card pictures could not be cleared. Une ou plusieurs images de cartes téléchargées n'ont pas pu être supprimées. - + Add URL Ajouter une URL - - + + URL: URL : - - + + Edit URL Modifier l'URL - + Network Cache Size: Taille du cache du réseau : - + Redirect Cache TTL: Rediriger le TTL du cache : - + How long cached redirects for urls are valid for. Combien de temps les redirections mises en cache pour les URL sont-elles valides. - + Picture Cache Size: Taille du cache des images : - + Add New URL Ajouter Nouveau Lien - + Remove URL Effacer Lien - + Day(s) Jour(s) - + Updating... Mise à jour... - + Choose path Choisir le chemin - + URL Download Priority URL de téléchargement prioritaire - + Spoilers Spoilers - + Download Spoilers Automatically Télécharger automatiquement les spoilers - + Spoiler Location: Emplacement des spoilers : - + Last Change Dernier changement - + Spoilers download automatically on launch Télécharger automatiquement les spoilers au lancement - + Press the button to manually update without relaunching Appuyez sur le bouton pour mettre à jour manuellement sans redémarrer. - + Do not close settings until manual update is complete Ne pas fermer la fenêtre des paramètres avant la mise à jour manuelle complète. - + Download card pictures on the fly Télécharger les images des cartes à la volée - + How to add a custom URL Comment ajouter une URL personnalisée - + Delete Downloaded Images Supprimer les images téléchargées - + Reset Download URLs Réinitialiser les URL de téléchargement - + On-disk cache for downloaded pictures Cache pour les images téléchargées - + In-memory cache for pictures not currently on screen Cache en mémoire pour les imges non affichées actuellement @@ -1498,32 +1469,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1531,27 +1502,27 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckListModel - + Count Compter - + Set Édition - + Number Nombre - + Provider ID Provider ID - + Card Carte @@ -1559,12 +1530,12 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckLoader - + Common deck formats (%1) Formats de decks courants (%1) - + All files (*.*) Tous les fichiers (*.*) @@ -1661,94 +1632,94 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckPreviewWidget - + Banner Card Carte bannière - + Open in deck editor Ouvrir dans l'éditeur de deck - + Edit Tags Éditer les étiquettes - + Rename Deck Renommer le deck - + Save Deck to Clipboard Copier le deck dans le presse-papier - + Annotated Avec annotations - + Annotated (No set info) Avec annotations (pas d'info sur l'extension) - + Not Annotated Sans annotation - + Not Annotated (No set info) Sans annotation (pas d'info sur l'extension) - + Rename File Renommer le fichier - + Delete File Supprimer le fichier - + Set Banner Card Choisir la carte bannière - - + + New name: Nouveau nom : - - + + Error Erreur - + Rename failed Renommage échoué - + Delete file Supprimer le fichier - + Are you sure you want to delete the selected file? Êtes-vous certain de vouloir supprimer le fichier sélectionné ? - + Delete failed Suppression échouée @@ -1781,32 +1752,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1873,30 +1844,30 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Réserve verrouillée - - + + Error Erreur - + The selected file could not be loaded. Le fichier sélectionné n'a pas pu être chargé. - + Deck is greater than maximum file size. Le deck est plus grand que la taille maximum de fichier. - + Are you sure you want to force start? This will kick all non-ready players from the game. Êtes-vous sûr de vouloir forcer le démarrage ? Cela va éjecter de la partie tous les joueurs non prêts à démarrer. - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditDeckInClipboard - + Edit deck in clipboard Éditer le deck dans le presse-papier - + Error Erreur - + Invalid deck list. Liste de deck invalide. @@ -2902,17 +2873,17 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgLoadDeckFromClipboard - + Load deck from clipboard Charger le deck depuis le presse-papier - + Error Erreur - + Invalid deck list. Liste de Deck invalide. @@ -2920,43 +2891,43 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Collez ici un lien vers un site de liste de deck pour l'importer.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) - - - - - + + + + + Load Deck from Website Charger un deck depuis un site web - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Aucun analyseur disponible pour ce fournisseur de deck.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) - + Network error: %1 Erreur réseau : %1 - + Received empty deck data. Données de deck vide reçues. - + Failed to parse deck data: %1 Échec de l'analyse des données du deck : %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2981,40 +2952,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Charger Deck + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nom de la carte (ou expression de recherche) : - + Number of hits: Nombre de cartes trouvées : - + Auto play hits Jouer les cartes trouvées automatiquement - + Put top cards on stack until... Placer la carte du dessus sur la pile jusqu'à... - + No cards matching the search expression exists in the card database. Proceed anyways? Aucune carte ne correspondant à l'expression recherchée n'existe dans la base de données de cartes. Continuer malgré tout ? - + Cockatrice Cockatrice - + Invalid filter Filtre invalide @@ -3175,12 +3179,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erreur inconnue lors du chargement de la base de données de cartes. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3197,7 +3201,7 @@ Vous devrez peut-être redémarrer Oracle pour mettre à jour votre base de donn Voulez-vous changer l'emplacement de votre base de données ? - + Your card database version is too old. This can cause problems loading card information or images @@ -3214,7 +3218,7 @@ Généralement, il suffit de redémarrer oracle pour mettre à jour votre base d Voulez-vous changer l'emplacement de votre base de données ? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3227,7 +3231,7 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues av Voulez-vous changer l'emplacement des données ? - + File Error loading your card database. Would you like to change your database location setting? @@ -3236,7 +3240,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3245,7 +3249,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3258,59 +3262,59 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues Voulez-vous changer l'emplacement de votre base de données ? - - - + + + Error Erreur - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire de decks est invalide. Voulez-vous redéfinir le chemin d'accès ? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire d'illustrations de cartes est invalide. Voulez-vous redéfinir le chemin d'accès ? - + Settings Paramètres - + General Général - + Appearance Apparence - + User Interface Interface utilisateur - + Card Sources Origine de carte - + Chat Chat - + Sound Son - + Shortcuts Raccourcis @@ -3627,67 +3631,67 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4135,143 +4139,143 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GeneralSettingsPage - + Reset all paths Réinitialiser tous les chemins - + All paths have been reset Les chemins ont été réinitialisés - - - - - - - + + + + + + + Choose path Choisir un chemin d'accès - + Personal settings Paramètres personnels - + Language: Langue : - + Paths (editing disabled in portable mode) Chemins (édition impossible en mode portable) - + Paths Chemins d’accès - + How to help with translations Comment aider avec les traductions - + Decks directory: Répertoire des decks : - + Filters directory: Répertoire des filtres : - + Replays directory: Répertoire des replays : - + Pictures directory: Répertoire des images : - + Card database: Base de données des cartes : - + Custom database directory: Répertoire de la base de données personnalisé - + Token database: Bases de données des jetons : - + Update channel Branche de la mise à jour : - + Check for client updates on startup Vérifier les mises à jour du client au démarrage - + Check for card database updates on startup Vérifier les mises à jour de la base de données des cartes au démarrage... - + Don't check Ne pas vérifier - + Prompt for update Confirmer les mises à jour - + Always update in the background Toujours mettre à jour en tâche de fond - + Check for card database updates every Vérifier les mises à jour de la base de données des cartes tous les - + days jour(s) - + Notify if a feature supported by the server is missing in my client M'avertir si une fonctionnalité supportée par le serveur est manquante sur mon client. - + Automatically run Oracle when running a new version of Cockatrice Lancer Oracle automatiquement quand Cockatrice a été mis à jour. - + Show tips on startup Afficher les astuces au démarrage. - + Last update check on %1 (%2 days ago) Dernière vérification de mise à jour le %1 (il y a %2 jours) @@ -4279,47 +4283,47 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GraveyardMenu - + &Graveyard &Cimetière - + &View graveyard &Voir le cimetière - + &Move graveyard to... &Déplacer le cimetière vers... - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &All players &Tous les joueurs - + &Hand &Main - + &Exile &Exil - + Reveal random card to... Révéler une carte au hasard à... @@ -4327,88 +4331,88 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. HandMenu - + &Hand &Main - + &View hand &Voir la main - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... &Déplacer la main vers… - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &Graveyard &Cimetière - + &Exile &Exil - + &Reveal hand to... &Révéler la main à... - - + + All players - + Reveal r&andom card to... Révéler une c&arte au hasard à... @@ -4416,52 +4420,52 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. HomeWidget - + Create New Deck Créer un Nouveau Deck - + Browse Decks Parcourir les Decks - + Browse Card Database Parcourir la Base de Carte - + Browse EDHRec Parcourir EDHRec - + Browse Archidekt - + View Replays Voir les replays - + Quit Quitter - + Connecting... Connection... - + Connect Se connecter - + Play Jouer @@ -4469,193 +4473,213 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. LibraryMenu - + &Library &Bibliothèque - + &View library &Voir la bibliothèque - + View &top cards of library... Voir les cartes du &dessus de la bibliothèque... - + View bottom cards of library... Voir les cartes du dessous de la bibliothèque... - + Reveal &library to... Révéler la &bibliothèque à... - + Lend library to... Prêter la bibliothèque à... - + Reveal &top cards to... Révéler les cartes du &dessus à... - + &Top of library... &Dessus de la bibliothèque... - + &Bottom of library... &Dessous de la bibliothèque... - + &Always reveal top card &Toujours révéler la carte du dessus - + &Always look at top card &Toujours voir la carte du dessus - + &Open deck in deck editor &Ouvrir le deck dans l'éditeur - + &Draw card &Piocher une carte - + D&raw cards... P&iocher plusieurs cartes... - + &Undo last draw &Annuler la dernière pioche - + Shuffle Mélanger - + &Play top card &Jouer la carte du dessus - + Play top card &face down Jouer la carte du dessus &face cachée - + Put top card on &bottom Placer la carte du dessus au-&dessous - + Move top card to grave&yard Mettre la carte du dessus dans le cime&tière - + Move top card to e&xile E&xiler la carte du dessus - + Move top cards to &graveyard... Mettre les cartes du dessus dans le &cimetière... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... &Exiler les cartes du dessus... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... Placer les cartes du dessus sur la pile &jusqu'à... - + Shuffle top cards... Mélanger les cartes du dessus... - + &Draw bottom card &Piocher la carte du dessous - + D&raw bottom cards... P&iocher les cartes du dessous... - + &Play bottom card &Jouer la carte du dessous - + Play bottom card &face down Jouer la carte du dessous &face cachée - + Move bottom card to grave&yard Mettre la carte du dessous dans le cime&tière - + Move bottom card to e&xile E&xiler la carte du dessous - + Move bottom cards to &graveyard... Mettre les cartes du dessus dans le &cimetière... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... &Exiler les cartes du dessous... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top Placer la carte du dessous au-&dessus - + Shuffle bottom cards... Mélanger les cartes du dessous... - - + + &All players &Tous les joueurs - + Reveal top cards of library Révéler les cartes du dessus de la bibliothèque - + Number of cards: (max. %1) Nombre de cartes : (max. %1) @@ -4755,18 +4779,8 @@ Will now login. Connexion en cours. - - Number of players - Nombre de joueurs - - - - Please enter the number of players. - Entrez s'il vous plaît le nombre de joueurs. - - - - + + Player %1 Joueur %1 @@ -4869,8 +4883,8 @@ Connexion en cours. - - + + Error Erreur @@ -5278,36 +5292,36 @@ La version locale est %1, la nouvelle version est %2. Montrer/Cacher - + New Version Nouvelle version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Bravo pour avoir mis à jour Cockatrice %1 ! Oracle va maintenant se lancer pour mettre à jour votre base de données de cartes. - + Cockatrice installed Installation de Cockatrice terminée - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Félicitations ! Vous venez d'installer Cockatrice %1 ! Oracle va maintenant se lancer pour installer la base de données de cartes initiale. - + Card database Base de données de cartes - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5316,29 +5330,29 @@ Voulez-vous la mettre à jour maintenant ? Si vous n’êtes pas sûr, ou si c'est la première fois que vous jouez, choisissez « Oui ». - - + + Yes Oui - - + + No Non - + Open settings Ouvrir les paramètres - + New sets found Nouvelles éditions détectées - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5351,17 +5365,17 @@ Code(s) d'édition(s) : %1 Voulez-vous l'(les) activer ? - + View sets Voir les éditions - + Welcome Bienvenue - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5370,65 +5384,65 @@ Toutes les éditions de la base de données de cartes ont été activées. Pour plus d'informations sur la modification de l'ordre des éditions, la désactivation d'éditions spécifiques, et leurs effets, consultez le menu « Gérer les éditions… ». - - + + Information Informations - + A card database update is already running. Une mise à jour de la base de données de cartes est déjà en cours. - + Unable to run the card database updater: Impossible de lancer la mise à jour de la base de données de cartes : - + Card database update running. Une mise à jour de la base de données de cartes est en cours. - + Failed to start. The file might be missing, or permissions might be incorrect. Échec au démarrage. Le fichier peut être manquant, ou les permissions sont incorrectes. - + The process crashed some time after starting successfully. Le processus a échoué quelque temps après avoir démarré avec succès. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Temps limite dépassé. Le processus a pris trop longtemps pour répondre. La dernière fonction waitFor...() a terminé. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal d'entrée fermé. - + An error occurred when attempting to read from the process. For example, the process may not be running. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal de sortie inactif. - + Unknown error occurred. Une erreur inconnue est arrivée. - + The card database updater exited with an error: %1 L'outil de mise à jour de la base de données de cartes s'est arrêté avec l'erreur : %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5439,55 +5453,55 @@ Ce n'est probablement pas un problème, mais ce message peut signifier qu&a Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - - - - - + + + + + Load sets/cards Charger des éditions/cartes - + Selected file cannot be found. Le fichier sélectionné n'a pas pu être trouvé. - + You can only import XML databases at this time. Il n'est actuellement possible que d'importer des bases de données XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Les nouvelles éditions/cartes ont été ajoutées avec succès. Cockactrice va maintenant recharger la base de données de cartes. - + Sets/cards failed to import. Échec de l'importation des éditions/cartes. - - - + + + Reset Password Réinitialiser le mot de passe - + Your password has been reset successfully, you can now log in using the new credentials. Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter en utilisant vos nouveaux identifiants. - + Failed to reset user account password, please contact the server operator to reset your password. Échec de la réinitialisation du mot de passe du compte. Veuillez contacter l'administrateur du serveur pour réinitialiser votre mot de passe. - + Activation request received, please check your email for an activation token. Demande d'activation reçue, vérifiez votre courrier électronique pour un jeton d'activation. @@ -5538,7 +5552,7 @@ Cockactrice va maintenant recharger la base de données de cartes. ManaBaseWidget - + Mana Base Base de mana @@ -5692,590 +5706,610 @@ Cockactrice va maintenant recharger la base de données de cartes. MessageLogWidget - + from play depuis le jeu - + from their graveyard depuis son cimetière - + from exile depuis l'exil - + from their hand depuis sa main - + the top card of %1's library la carte du dessus de la bibliothèque de %1 - + the top card of their library le carte du dessus de sa bibliothèque - + from the top of %1's library à partir du dessus de la bibliothèque de %1 - + from the top of their library du dessus de sa bibliothèque - + the bottom card of %1's library la carte du dessous de la bibliothèque de %1 - + the bottom card of their library la carte du dessous de sa bibliothèque - + from the bottom of %1's library à partir du dessous de la bibliothèque de %1 - + from the bottom of their library du dessous de sa bibliothèque - + from %1's library de la bibliothèque de %1 - + from their library depuis sa bibliothèque - + from sideboard depuis sa réserve - + from the stack depuis la pile - + from custom zone '%1' depuis la zone personnalisée '%1' - + %1 is now keeping the top card %2 revealed. %1 garde maintenant la carte du dessus de sa bibliothèque %2 révélée. - + %1 is not revealing the top card %2 any longer. %1 ne révèle plus la carte du dessus de sa bibliothèque %2. - + %1 can now look at top card %2 at any time. %1 peut maintenant regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 no longer can look at top card %2 at any time. %1 ne peut plus regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 attaches %2 to %3's %4. %1 attache %2 à %4 de %3. - + %1 has conceded the game. %1 a concédé la partie. - + %1 has unconceded the game. %1 reste dans la partie. - + %1 has restored connection to the game. %1 est revenu dans la partie. - + %1 has lost connection to the game. %1 a perdu la connexion à la partie. - + %1 points from their %2 to themselves. %1 se cible avec son %2. - + %1 points from their %2 to %3. %1 cible %3 avec son %2. - + %1 points from %2's %3 to themselves. %1 se cible avec %3 de %2. - + %1 points from %2's %3 to %4. %1 cible %4 avec %3 de %2. - + %1 points from their %2 to their %3. %1 cible de son %3 vers son %2. - + %1 points from their %2 to %3's %4. %1 cible %3 de %4 avec %2. - + %1 points from %2's %3 to their own %4. %1 cible son %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. %1 cible %5 de %4 avec %3 de %2. - + %1 creates a face down token. %1 crée un jeton face-cachée. - + %1 creates token: %2%3. %1 crée un jeton %2%3. - + %1 has loaded a deck (%2). %1 a chargé un deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 a chargé un deck avec %2 cartes en réserve (%3). - + %1 destroys %2. %1 détruit %2. - + a card une carte - + %1 gives %2 control over %3. %1 donne le contrôle de %2 à %3. - + %1 puts %2 into play%3 face down. %1 met %2 en jeu%3 face cachée. - + %1 puts %2 into play%3. %1 met %2 en jeu %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 met %2%3 dans son cimetière. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 exile %2%3. - + %1 moves %2%3 to their hand. %1 met %2%3 dans sa main. - + %1 puts %2%3 into their library. %1 met %2%3 dans sa bibliothèque. - + %1 puts %2%3 onto the bottom of their library. %1 met %2%3 au-dessous de sa bibliothèque. - + %1 puts %2%3 on top of their library. %1 met %2%3 au-dessus de sa bibliothèque. - + %1 puts %2%3 into their library %4 cards from the top. %1 met %2%3 dans sa bibliothèque %4 cartes depuis le dessus - + %1 moves %2%3 to sideboard. %1 met %2%3 à sa réserve. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 joue %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. %1 déplace %2%3 vers la zone personnalisée '%4'. - + %1 tries to draw from an empty library %1 essaie de piocher dans une bibliothèque vide - + %1 draws %2 card(s). %1 pioche %2 carte.%1 pioche %2 cartes.%1 pioche %2 carte(s). - + %1 is looking at %2. %1 regarde %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 regarde la %4 %3 carte %2.%1 regarde les %4 %3 cartes %2.%1 regarde la / les %4 %3 carte(s) %2. - + bottom dessous - + top dessus - + %1 turns %2 face-down. %1 retourne %2 face cachée. - + %1 turns %2 face-up. %1 retourne %2 face visible. - + The game has been closed. La partie a été fermée. - + The game has started. La partie commence. - + You are flooding the game. Please wait a couple of seconds. Vous floodez la partie. Veuillez patienter quelques secondes. - + %1 has joined the game. %1 a rejoint la partie. - + %1 is now watching the game. %1 est maintenant spectateur. - + You have been kicked out of the game. Vous avez été expulsé de la partie. - + %1 has left the game (%2). %1 a quitté la partie (%2). - + %1 is not watching the game any more (%2). %1 n'observe plus la partie (%2). - + %1 is not ready to start the game any more. %1 n'est plus prêt à démarrer la partie. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mélange son deck et pioche une nouvelle main de %2 carte.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 cartes.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 carte(s). - + %1 shuffles their deck and draws a new hand. %1 mélange sa bibliothèque et pioche une nouvelle main. - + You are watching a replay of game #%1. Vous regardez un replay de la partie n° %1. - + %1 is ready to start the game. %1 est prêt à démarrer la partie. - + cards an unknown amount of cards cartes - + %1 card(s) a card for singular, %1 cards for plural %1 carte%1 cartes%1 carte(s) - + %1 lends %2 to %3. %1 prêtes %2 à %3. - + %1 reveals %2 to %3. %1 révèle %2 à %3. - + %1 reveals %2. %1 révèle %2. - + %1 randomly reveals %2%3 to %4. %1 révèle au hasard %2%3 à %4. - + %1 randomly reveals %2%3. %1 révèle au hasard %2%3. - + %1 peeks at face down card #%2. %1 regarde furtivement la carte face cachée n° %2. - + %1 peeks at face down card #%2: %3. %1 regarde furtivement la carte face cachée n° %2 : %3. - + %1 reveals %2%3 to %4. %1 révèle %2%3 à %4. - + %1 reveals %2%3. %1 révèle %2%3. - + %1 reversed turn order, now it's %2. %1 a inversé l'ordre des tours, c'est maintenant %2. - + reversed inversé - + normal normal - + Heads Face - + Tails Pile - + %1 flipped a coin. It landed as %2. %1 a lancé une pièce. Il a fait %2. - + %1 rolls a %2 with a %3-sided die. %1 a fait %2 avec un dé %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lances %2 pièces. Il y'a %3 faces et %4 piles. - + %1 rolls a %2-sided dice %3 times: %4. %1 lances un dé à %2-faces %3 fois: %4. - + %1's turn. Tour de %1 - + %1 sets annotation of %2 to %3. %1 met l'annotation %3 à %2. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 place %2 "%3" compteur sur %4 (désormais %5).%1 place %2 "%3" compteurs sur %4 (désormais %5).%1 place %2 "%3" compteur(s) sur %4 (désormais %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 retire %2 "%3" compteur sur %4 (désormais %5).%1 retire %2 "%3" compteurs sur %4 (désormais %5).%1 retire %2 "%3" compteur(s) sur %4 (désormais %5). - + %1 sets counter %2 to %3 (%4%5). %1 met les marqueurs %2 à %3 (%4%5). - + %1 sets %2 to not untap normally. %2 de %1 ne se dégagera pas lors de l'étape de dégagement. - + %1 sets %2 to untap normally. %2 de %1 se dégagera lors de l'étape de dégagement. - + %1 removes the PT of %2. %1 retire la F/E de %2. - + %1 changes the PT of %2 from nothing to %4. %1 met la F/E de %2 à %4. - + %1 changes the PT of %2 from %3 to %4. %1 passe la F/E de %2 de %3 à %4. - + %1 has locked their sideboard. %1 a verrouillé sa réserve. - + %1 has unlocked their sideboard. %1 a déverrouillé sa réserve. - + %1 taps their permanents. %1 engage ses permanents. - + %1 untaps their permanents. %1 dégage ses permanents. - + %1 taps %2. %1 engage %2. - + %1 untaps %2. %1 dégage %2. - + %1 shuffles %2. %1 mélange %2. - + %1 shuffles the bottom %3 cards of %2. %1 mélange les %3 cartes du dessous de la bibliothèque de %2. - + %1 shuffles the top %3 cards of %2. %1 mélange les %3 cartes du dessus de la bibliothèque de %2. - + %1 shuffles cards %3 - %4 of %2. %1 mélange les cartes %3 - %4 de %2. - + %1 unattaches %2. %1 détache %2. - + %1 undoes their last draw. %1 annule sa dernière pioche. - + %1 undoes their last draw (%2). %1 annule sa dernière pioche (%2). @@ -6283,110 +6317,110 @@ Cockactrice va maintenant recharger la base de données de cartes. MessagesSettingsPage - + Word1 Word2 Word3 Mot1 Mot2 Mot3 - + Add New Message Ajouter un message - + Edit Message Éditer le message - + Remove Message Supprimer le message - + Add message Ajouter message - - + + Message: Message : - + Edit message Éditer le message - + Chat settings Paramètres du chat - + Custom alert words Mots d'alertes personnalisées - + Enable chat mentions Activer les mentions dans le chat - + Enable mention completer Activer les mentions dans le chat - + In-game message macros Macros de message en jeu - + How to use in-game message macros Comment utiliser les macro pour les messages en jeu - + Ignore chat room messages sent by unregistered users Ignorer les messages dans le chat principal envoyés par des joueurs non enregistrés. - + Ignore private messages sent by unregistered users Ignorer les messages privés envoyés par des joueurs non enregistrés. - - + + Invert text color Inverser la couleur du texte - + Enable desktop notifications for private messages Activer les notifications de bureau pour les messages privés. - + Enable desktop notification for mentions Activer les notifications de bureau pour les mentions. - + Enable room message history on join Activer l'historique des messages du chat principal dès la connexion - - + + (Color is hexadecimal) (La couleur est hexadécimale) - + Separate words with a space, alphanumeric characters only Séparer les mots avec un espace, seulement les caractères alphanumériques @@ -6399,32 +6433,37 @@ Cockactrice va maintenant recharger la base de données de cartes. Déplacer vers - + &Top of library in random order &Dessus de la bibliothèque dans un ordre aléatoire - + X cards from the top of library... X cartes du dessus de la bibliothèque... - + &Bottom of library in random order &Dessous de la bibliothèque dans un ordre aléatoire - + + T&able + + + + &Hand &Main - + &Graveyard &Cimetière - + &Exile &Exil @@ -6566,57 +6605,57 @@ Cockactrice va maintenant recharger la base de données de cartes. PhasesToolbar - + Untap step Étape de dégagement - + Upkeep step Étape d'entretien - + Draw step Étape de pioche - + First main phase Première phase principale - + Beginning of combat step Étape de début de combat - + Declare attackers step Étape de déclaration des attaquants - + Declare blockers step Étape de déclaration des bloqueurs - + Combat damage step Étape d'assignation des blessures de combat - + End of combat step Étape de fin de combat - + Second main phase Seconde phase principale - + End of turn step Phase de fin de tour @@ -6633,134 +6672,138 @@ Cockactrice va maintenant recharger la base de données de cartes. PlayerActions - + View top cards of library Voir les cartes du dessus de la bibliothèque - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Nombre de cartes : (max. %1) - + View bottom cards of library Voir les cartes du dessous de la bibliothèque - + Shuffle top cards of library Mélanger les cartes du dessus de la bibliothèque - + Shuffle bottom cards of library Mélanger les cartes du dessous de la bibliothèque - + Draw hand Piocher une main - + 0 and lower are in comparison to current hand size 0 et moins sont en comparaison avec la taille actuelle de la main - + Draw cards Piocher des cartes + + + + + + grave + + - Move top cards to grave - Mettre les cartes du dessus dans le cimetière + + + + exile + - - Move top cards to exile - Exiler les cartes du dessus + + Move top cards to %1 + - - Move bottom cards to grave - Mettre les cartes du dessous dans le cimetière + + Move bottom cards to %1 + - - Move bottom cards to exile - Exiler les cartes du dessous - - - + Draw bottom cards Piocher les cartes du dessous - - + + C&reate another %1 token C&réer un autre jeton %1 - + Create tokens Créer des jetons - - + + Number: Nombre : - + Place card X cards from top of library Placer les X cartes depuis le dessus de la bibliothèque - + Which position should this card be placed: Dans quelle position cette carte doit-elle être placée : - + (max. %1) (max. %1) - + Change power/toughness Changer la force/l'endurance - + Change stats to: Changer les stats pour : - + Set annotation Annoter - + Please enter the new annotation: Veuillez entrer la nouvelle annotation : - + Set counters Définir les marqueurs @@ -6768,48 +6811,65 @@ Cockactrice va maintenant recharger la base de données de cartes. PlayerMenu - + Player "%1" Joueur "%1" - + &Counters &Marqueurs + + + PrintingDisabledInfoWidget - - S&ay - D&ire + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again + PrintingSelector - + Display Navigation Buttons Afficher les boutons de navigation + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference Préférence - + Pin Printing Épingler Imprimer - + Unpin Printing Désépingler Imprimer - + Show Related cards Afficher les cartes associées @@ -6858,17 +6918,25 @@ Cockactrice va maintenant recharger la base de données de cartes. Date de Sortie - - + + Descending Descendant - + Ascending Ascendant + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -7007,6 +7075,33 @@ Cockactrice va maintenant recharger la base de données de cartes. A .cod version of this deck already exists. Overwrite it? Une version en fichier .cod existe déjà pour ce deck. L'écraser ? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7155,37 +7250,37 @@ Cockactrice va maintenant recharger la base de données de cartes. RfgMenu - + &Exile &Exil - + &View exile &Voir la zone d'exil - + &Move exile to... &Déplacer la zone d'exil vers... - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &Hand &Main - + &Graveyard &Cimetière @@ -7228,6 +7323,14 @@ Cockactrice va maintenant recharger la base de données de cartes. Parties + + SayMenu + + + S&ay + + + SequenceEdit @@ -7292,53 +7395,53 @@ Cockactrice va maintenant recharger la base de données de cartes. ShortcutSettingsPage - - + + Restore all default shortcuts Réinitialiser tous les raccourcis - + Do you really want to restore all default shortcuts? Êtes-vous sûr de vouloir réinitialiser tous les raccourcis ? - + Clear all default shortcuts Effacer tous les raccourcis par défaut - + Do you really want to clear all shortcuts? Êtes-vous sûr de vouloir effacer tous les raccourcis ? - + Section: Section : - + Action: Action : - + Shortcut: Raccourci : - + How to set custom shortcuts Comment assigner des raccourcis personnalisés - + Clear all shortcuts Effacer tous les raccourcis - + Search by shortcut name Rechercher par nom de raccourci @@ -7406,27 +7509,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Activer les &sons - + Current sounds theme: Thème sonore actuel: - + Test system sound engine Tester les effets sonores - + Sound settings Paramètres de son - + Master volume Volume principal @@ -7642,59 +7745,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7702,60 +7869,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Infos de la carte - + Deck Bibliothèque - + Filters Filtres - + &View &Voir - + Card Database - + Printing Imprimer - - - - - + Visible Visible - - - - - + Floating Flottant - + Reset layout Réinitialiser l'interface - + Deck: %1 Deck : %1 @@ -7763,61 +7922,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Deck visuel : %1 - + &Visual Deck Editor &Éditeur de deck visuel - - + + Card Info Infos de la carte - - + + Deck Deck - - + + Filters Filtres - + &View &Voir - + Printing Imprimer - - - - + Visible Visible - - - - + Floating Flottant - + Reset layout Réinitialiser l'interface @@ -7882,7 +8035,7 @@ Please check your shortcut settings! - + New folder Nouveau dossier @@ -7964,18 +8117,18 @@ Veuillez entrer un nom: Êtes-vous certain de vouloir supprimer les fichiers sélectionnés ? - + Delete remote decks Supprimer les decks dinstants - + Are you sure you want to delete the selected decks? Êtes-vous certain de vouloir supprimer les decks sélectionnés ? - + Name of new folder: Nom du nouveau dossier : @@ -7993,12 +8146,12 @@ Veuillez entrer un nom: Stockage visuel de deck - + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 @@ -8047,197 +8200,191 @@ Veuillez entrer un nom: TabGame - - - + + + Replay Replay - - + + Game Partie - - + + Player List Liste des joueurs - - + + Card Info Infos de la carte - - + + Messages Messages - - + + Replay Timeline Historique des replays - + &Phases &Phases - + &Game &Partie - + Next &phase Étape &suivante - + Next phase with &action Étape suivante avec &action - + Next &turn &Tour suivant - + Reverse turn order Inverser l'ordre des tours - + &Remove all local arrows &Retirer toutes les flèches locales - + Rotate View Cl&ockwise Pivoter la vue dans le sens h&oraire - + Rotate View Co&unterclockwise Pivoter la vue dans le sens &anti-horaire - + Game &information &Informations sur la partie - + Un&concede Annuler la &concession - - - + + + &Concede &Concéder - + &Leave game &Quitter la partie - + C&lose replay Fermer &le replay - + &Focus Chat &Focus sur le chat - + &Say: &Dire : - + Selected cards Cartes sélectionnées - + &View &Voir - - - - + Visible Visible - - - - + Floating Flottant - + Reset layout Réinitialiser l'interface - + Concede Concéder - + Are you sure you want to concede this game? Êtes-vous sûr de vouloir concéder la partie ? - + Unconcede Rester dans la partie - + You have already conceded. Do you want to return to this game? Vous avez déjà concédé la partie. Voulez-vous la reprendre ? - + Leave game Quitter la partie - + Are you sure you want to leave this game? Êtes-vous sûr de vouloir quitter la partie ? - + A player has joined game #%1 Un joueur a rejoint la partie #%1. - + %1 has joined the game %1 a rejoint la partie. - + You have been kicked out of the game. Vous avez été expulsé de la partie. @@ -9339,142 +9486,152 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserInterfaceSettingsPage - + General interface settings Paramètres généraux de l'interface - + &Double-click cards to play them (instead of single-click) &Double cliquer sur les cartes pour les jouer (au lieu d'un simple clic) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliquer joue toutes les cartes sélectionnées (à la place de seulement la carte cliquée) - + &Play all nonlands onto the stack (not the battlefield) by default &Jouer toutes les cartes non terrain dans la pile (pas sur le champ de bataille) par défaut - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed Fermer la fenêtre d'affichage des cartes lorsque la dernière carte est supprimée - + Auto focus search bar when card view window is opened Focalise automatiquement la barre de recherche lorsque la fenêtre d'affichage des cartes est ouverte - + Annotate card text on tokens Annoter le texte des cartes sur les jetons - + + Show selection counter during drag selection + + + + + Show total selection counter + + + + Use tear-off menus, allowing right click menus to persist on screen Utiliser les menus détachables, permettant les clics droits sur les rendre persistants - + Notifications settings Paramètres des notifications - + Enable notifications in taskbar Activer les notifications dans la barre des tâches - + Notify in the taskbar for game events while you are spectating Notifier dans la barre des tâches pour les évènements des parties quand vous y êtes spectateur - + Notify in the taskbar when users in your buddy list connect Notifier dans la barre des tâches quand un ami se connecte - + Animation settings Paramètres des animations - + &Tap/untap animation &Animation d'engagement / dégagement - + Deck editor/storage settings Paramètres d'éditeur et de stockage de deck - + Open deck in new tab by default Ouvre le deck dans un nouvel onglet par défaut - + Use visual deck storage in game lobby Utiliser le stockage visuel de decks dans le lobby des parties - + Use selection animation for Visual Deck Storage Utilisez l'animation de sélection pour le stockage visuel des deck - + When adding a tag in the visual deck storage to a .txt deck: Lorsque vous ajoutez une étiquette dans le stockage visuel du deck à un deck stocké comme un fichier .txt : - + do nothing ne rien faire - + ask to convert to .cod demander à convertir en fichier .cod - + always convert to .cod toujours convertir en fichier .cod - + Default deck editor type Type d'éditeur de deck par défaut - + Classic Deck Editor Éditeur de deck classique - + Visual Deck Editor Éditeur de deck visuel - + Replay settings Paramètres du replay - + Buffer time for backwards skip via shortcut: Temps de tampon pour les sauts en arrière via un raccourci : @@ -9538,24 +9695,25 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match - Mode : correspondance exacte + + Exact match + + + + + Includes + - Mode: Includes - Mode : inclure + Include / Exclude + Mode: Includes + - - Mode: Include/Exclude - Mode : inclure / exclure - - - - Filter mode (AND/OR/NOT conjunctions of filters) - Mode de filtre (conjonctions AND/OR/NOT de filtres) + + How selected and unselected colors are combined in the filter + @@ -9581,25 +9739,108 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Entrer le nom du fichier... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9607,22 +9848,32 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database Ne pas afficher les types principaux de cartes dont le nombre est inférieur à ce montant dans la base de données - + Filter mode (AND/OR/NOT conjunctions of filters) Mode de filtre (conjonctions AND/OR/NOT de filtres) - + Mode: Exact Match Mode : correspondance exacte - + Mode: Includes Mode : inclure @@ -9658,7 +9909,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtrer par extensions récentes @@ -9666,19 +9917,19 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplaySetFilterWidget - + Search sets... Rechercher les extensions... - - + + Mode: Exact Match Mode : correspondance exacte - - + + Mode: Includes Mode : inclure @@ -9686,27 +9937,37 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... Rechercher par sous-types... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database Ne pas afficher les sous-types de cartes dont le nombre est inférieur à ce montant dans la base de données - + Filter mode (AND/OR/NOT conjunctions of filters) Mode de filtre (conjonctions AND/OR/NOT de filtres) - + Mode: Exact Match Mode : correspondance exacte - + Mode: Includes Mode : inclure @@ -9720,52 +9981,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - + Visual - + Loading database ... Chargement de la base de données... - + Clear all filters Effacer tous les filtres - - Sort by: - - - - - Filter by: - - - - - Save and load filters - Sauvegarder et charger des filtres - - - - Filter by exact card name - Filtrer par nom exact de carte - - - - Filter by card sub-type - Filtrer par sous-type de carte - - - - Filter by set - Filtrer par extension - - - + Table @@ -9773,56 +10004,64 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Piocher un nouvel exemple de main - + Sample hand size Taille de l'exemple de main @@ -9830,17 +10069,17 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card Recherche et ajout rapide d'une carte - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Recherchez la correspondance la plus proche dans la base de données (avec suggestions automatiques) et ajoutez l'impression souhaitée au deck en appuyant sur Entrée @@ -9856,47 +10095,52 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageQuickSettingsWidget - + Show Folders Montrer les dossiers - + Show Tag Filter Montrer le filtre à étiquette - + + Show Color Identity + + + + Show Tags On Deck Previews Montrer les étiquettes sur les aperçus de decks - + Show Banner Card Selection Option Montrer l'option de sélection de carte bannière - + Draw unused Color Identities Affiche les identités de couleurs non utilisées - + Unused Color Identities Opacity Opacité des identités de couleur inutilisées - + Deck tooltip: Aide contextuelle du deck : - + None Aucun(e) - + Filepath Chemin de fichier @@ -9997,133 +10241,133 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre WndSets - + Move selected set to the top Déplacer l'édition sélectionnée tout en haut - + Move selected set up Déplacer l'édition sélectionnée vers le haut - + Move selected set down Déplacer l'édition sélectionnée vers le bas - + Move selected set to the bottom Déplacer l'édition sélectionnée tout en bas - + Search by set name, code, or type Rechercher par ensemble de nom, code ou type - + Default order Ordre par défaut - + Restore original art priority order Restaurer l'ordre original de priorité artistique - + Enable all sets Activer toutes les éditions - + Disable all sets Désactiver toutes les éditions - + Enable selected set(s) Activer l(es) édition(s) sélectionnée(s) - + Disable selected set(s) Désactiver l(es) éditions(s) sélectionné(s) - + Deck Editor Éditeur de deck - + Use CTRL+A to select all sets in the view. Utiliser Ctrl+A pour sélectionner toutes les éditions dans la vue actuelle. - + Only cards in enabled sets will appear in the card list of the deck editor. Seules les cartes des éditions activées apparaitront dans la liste de cartes de l'éditeur de deck. - + Image priority is decided in the following order: La priorité des images est décidé dans l'ordre suivant: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki D'abord le dossier PERSONNALISÉ (%1), puis les éditions activées dans cette fenêtre (de haut en bas) - + Include cards rebalanced for Alchemy [requires restart] Inclure les cartes rééquilibrées pour Alchemy [nécessite un redémarrage] - + Card Art Images des cartes - + How to use custom card art Comment utiliser une illustration personnalisée de carte - + Hints Astuces - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Trier par colonne vous permet de trouver une édition sans changer la priorité des éditions. - + To enable ordering again, click the column header until this message disappears. Pour trier à nouveau, cliquez sur l'entête de la colonne jusqu'à ce que ce message disparaisse. - + Use the current sorting as the set priority instead Utiliser le tri actuel comme ordre de priorité des éditions - + Sorts the set priority using the same column Sortir la priorité sur la même colonne - + Manage sets Gérer les éditions @@ -10131,72 +10375,72 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre ZoneViewWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + Ungrouped Non groupé - + Group by Type Grouper par Types - + Group by Mana Value Grouper par Valeur de Mana - + Group by Color Grouper par Couleurs - + Unsorted Non trié - + Sort by Name Trier par Nom - + Sort by Type Trier par Type - + Sort by Mana Cost Trier par Coût de Mana - + Sort by Colors Trier par Couleurs - + Sort by P/T Trier par F/E - + Sort by Set Trier par extension - + shuffle when closing mélanger en quittant - + pile view vue de la pile @@ -10231,7 +10475,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - + Deck Editor Éditeur de deck @@ -10312,7 +10556,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - + Replays Replays @@ -10464,7 +10708,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - + Reset Layout Réinitialiser l'interface @@ -10885,8 +11129,9 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - Toggle Untap - Activer / désactiver le dégagement normal + Toggle Skip Untapping + Toggle Untap + @@ -10905,98 +11150,102 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre + Play Card, Face Down + + + + Attach Card... Attacher la carte… - + Unattach Card Détacher la carte - + Clone Card Cloner la carte - + Create Token... Créer un jeton… - + Create All Related Tokens Créer tous les jetons associés - + Create Another Token Créer un autre jeton - + Set Annotation... Annoter… - + Select All Cards in Zone Sélectionne toutes les cartes dans la zone - + Select All Cards in Row Sélectionne toutes les cartes dans la ligne - + Select All Cards in Column Sélectionne toutes les cartes dans la colonne - + Reveal Selected Cards to All Players - - + + Bottom of Library Dessous de la bibliothèque - + - - + + Exile Exil - + - + Graveyard Cimetière - + Hand Main - - + + Top of Library Dessus de la bibliothèque - - + Battlefield, Face Down Champ de bataille, face cachée @@ -11032,234 +11281,246 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre - + Stack Pile - + Graveyard (Multiple) Cimetière (plusieurs) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Exil (plusieurs) - + + + Exile (Multiple), Face Down + + + + Stack Until Found Empiler jusqu'à ce que trouvé - + Draw Bottom Card Piocher la carte du dessous - + Draw Multiple Cards from Bottom... Piocher plusieurs cartes du dessous... - + Draw Arrow... Tracer une flèche… - + Remove Local Arrows Retirer les flèches locales - + Leave Game Quitter la partie - + Concede Concéder - + Roll Dice... Lancer un dé… - + Shuffle Library Mélanger la bibliothèque - + Shuffle Top Cards of Library Mélanger les cartes du dessus de la bibliothèque - + Shuffle Bottom Cards of Library Mélanger les cartes du dessous de la bibliothèque - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Piocher une carte - + Draw Multiple Cards... Piocher plusieurs cartes… - + Undo Draw Annuler la dernière pioche - + Always Reveal Top Card Toujours révéler la carte du dessus - + Always Look At Top Card Toujours révéler la carte du dessus - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Pivoter la vue dans le sens horaire - + Rotate View Counterclockwise Pivoter la vue dans le sens anti-horaire - + Unfocus Text Box Lâcher le focus de la boîte de texte - + Focus Chat Focus sur le chat - + Clear Chat Effacer le chat - + Refresh Rafraichir - + Skip Forward Avance en avant - + Skip Backward Avant en arrière - + Skip Forward by a lot Avance beaucoup en avant - + Skip Backward by a lot Avance beaucoup en arrière - + Play/Pause Jouer/Pause - + Toggle Fast Forward Activer l'avance rapide - + Home Accueil - + Visual Deck Storage Stockage visuel de deck - + Deck Storage Stockage de deck - + Server Serveur - + Account Compte - + Administration Administration - + Logs Journaux diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 5893d4121..3990d532f 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Ricarica - + Parse Set Name and Number (if available) Acquisisci set e numero (se disponibili) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Apri in una nuova scheda - + Are you sure? Sei sicuro? - + The decklist has been modified. Do you want to save the changes? La lista del mazzo è stata modificata. Vuoi salvare i cambiamenti? - - - - - - + + + + + + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 - + Could not save remote deck Impossibile salvare mazzo remoto - - + + The deck could not be saved. Please check that the directory is writable and try again. Il mazzo non può essere salvato. Controlla se la cartella è valida e prova ancora. - + Save deck Salva mazzo - + The deck could not be saved. Il mazzo non può essere salvato. - + There are no cards in your deck to be exported Non ci sono carte da esportare nel tuo mazzo @@ -133,202 +133,168 @@ Controlla se la cartella è valida e prova ancora. AppearanceSettingsPage - + seconds secondi - + Error Errore - + Could not create themes directory at '%1'. Impossibile creare la cartella dei temi in '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Abilitare questa funzione disattiverà il Selettore di stampa. - -Non potrai gestire le preferenze delle stampe per i singoli mazzi, o vedere le stampe scelte dagli altri giocatori per i loro mazzi. - -Dovrai usare il Gestore dei set, raggiungibile tramite Database carte -> Organizza set. - -Sicuro di voler abilitare questa funzione? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Disabilitare questa funzione attiverà il Selettore di stampa. - -Potrai gestire le preferenze delle stampe per i singoli mazzi nell'Editor, e configurare quale stampa viene aggiunta di default a un mazzo fissandola nel selettore. - -Potrai anche usare il Gestore dei set per personalizzare l'ordine delle stampe nel Selettore (sono disponibili anche ordinamenti predefiniti come alfabetico o per data di uscita). - -Sicuro di voler disabilitare questa funzione? - - - - Confirm Change - Conferma modifica - - - + Theme settings Impostazioni temi - + Current theme: Tema attuale: - + Open themes folder Apri cartella temi - + Home tab background source: Sorgente dello sfondo della Home: - + Home tab background shuffle frequency: Frequenza di modifica dello sfondo della Home: - + Disabled Disattivato - + + Display card name of background in bottom right: + Mostra nome della carta di sfondo in basso a destra + + + Menu settings Impostazioni menù - + Show keyboard shortcuts in right-click menus Mostra scorciatoie da tastiera nel menù del tasto destro del mouse - + Show game filter toolbar above list in room tab Mostra barra di filtro sopra la lista delle partite - + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sopra le immagini - + Auto-Rotate cards with sideways layout Ruota automaticamente carte con disposizione orizzontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) Determina l'illustrazione della carta attraverso la priorità dei set (come prima dell'implementazione del ProviderID) - + Bump sets that the deck contains cards from to the top in the printing selector Nel selettore di stampa, mostra per primi i set delle carte che il mazzo contiene - + Scale cards on mouse over Ingrandisci la carta sotto il mouse - + Use rounded card corners Usa i bordi delle carte arrotondati - + Minimum overlap percentage of cards on the stack and in vertical hand Sovrapposizione % minima delle carte in pila e nella mano verticale: - + Maximum initial height for card view window: Altezza iniziale massima per la finestra di visualizzazione delle carte: - - + + rows file - + Maximum expanded height for card view window: Altezza massima per la finestra di visualizzazione delle carte: - + Card counters Segnalini della carta - + Counter %1 Segnalino %1 - + Hand layout Disposizione della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente (spreca spazio) - + Enable left justification Allinea a sinistra - + Table grid layout Disposizione delle aree di gioco - + Invert vertical coordinate Inverti disposizione verticale - + Minimum player count for multi-column layout: Numero di giocatori minimo per disposizione multicolonna: - + Maximum font size for information displayed on cards: Dimensione massima carattere per le informazioni mostrate sulle carte: @@ -336,7 +302,12 @@ Sicuro di voler disabilitare questa funzione? ArchidektApiResponseDeckDisplayWidget - + + Back to results + Torna ai risultati + + + Open Deck in Deck Editor Apri mazzo nell'editor dei mazzi @@ -656,22 +627,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoPictureWidget - + View related cards Guarda carte correlate - + Add card to deck Aggiungi la carta al &mazzo - + Mainboard Mazzo - + Sideboard Sideboard @@ -707,124 +678,124 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardMenu - + Re&veal to... Ri&vela a... - + &All players &Tutti i giocatori - + View related cards Guarda carte correlate - + Token: Pedina: - + All tokens Tutte le pedine - + &Select All &Seleziona tutto - + S&elect Row S&eleziona fila - + S&elect Column S&eleziona colonna - + &Play &Gioca - + &Hide &Nascondi - + Play &Face Down Gioca a &faccia in giù - + &Tap / Untap Turn sideways or back again &TAPpa/STAPpa - - Toggle &normal untapping - Blocca/Sblocca &STAP normale + + Skip &untapping + Salta lo &STAP - + T&urn Over Turn face up/face down Capovolgi - + &Peek at card face &Sbircia la faccia della carta - + &Clone &Copia - + Attac&h to card... Asse&gna alla carta... - + Unattac&h Tog&li - + &Draw arrow... &Disegna una freccia... - + &Set annotation... &Imposta note... - + Ca&rd counters Segnalini delle ca&rte - + &Add counter (%1) &Aggiungi segnalino (%1) - + &Remove counter (%1) &Rimuovi segnalino (%1) - + &Set counters (%1)... Imposta &segnalini (%1)... @@ -840,133 +811,133 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardZoneLogic - + their hand nominative la sua mano - + %1's hand nominative Mano di %1 - + their library look at zone il suo grimorio - + %1's library look at zone Grimorio di %1 - + of their library top cards of zone, del suo grimorio - + of %1's library top cards of zone del grimorio di %1 - + their library reveal zone il suo grimorio - + %1's library reveal zone Grimorio di %1 - + their library shuffle il suo grimorio - + %1's library shuffle Grimorio di %1 - - - their library - nominative - il suo grimorio - - - - %1's library - nominative - Grimorio di %1 - + their library + nominative + il suo grimorio + + + + %1's library + nominative + Grimorio di %1 + + + their graveyard nominative il suo cimitero - + %1's graveyard nominative Cimitero di %1 - + their exile nominative la sua zona di esilio - + %1's exile nominative Esilio di %1 - - - their sideboard - look at zone - la sua sideboard - - - - %1's sideboard - look at zone - Sideboard di %1 - their sideboard - nominative + look at zone la sua sideboard %1's sideboard + look at zone + Sideboard di %1 + + + + their sideboard + nominative + la sua sideboard + + + + %1's sideboard nominative Sideboard di %1 - + their custom zone '%1' nominative la sua zona personalizzata '%1' - + %1's custom zone '%2' nominative la zona personalizzata '%2' di %1 @@ -1028,7 +999,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorCardDatabaseDockWidget - + Card Database Database carte @@ -1036,7 +1007,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorCardInfoDockWidget - + Card Info Info carta @@ -1059,32 +1030,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Aggiungi alla sideboard - + Select Printing Seleziona stampa - + Show on EDHRec (Commander) Mostra su EDHRec (Commander) - + Show on EDHRec (Card) Mostra su EDHRec (Carta) - + Show Related cards Mostra carte correlate - + Add card to &maindeck Aggiungi carta al &mazzo - + Add card to &sideboard Aggiungi carta alla &sideboard @@ -1092,32 +1063,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorDeckDockWidget - + Loading Database... Caricamento database... - + Banner Card Carta copertina - + Main Type Tipo - + Mana Cost Costo di mana - + Colors Colori - + Select Printing Seleziona stampa @@ -1190,17 +1161,17 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorFilterDockWidget - + Filters Filtri - + &Clear all filters &Elimina tutti i filtri - + Delete selected Elimina filtro @@ -1323,7 +1294,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorPrintingSelectorDockWidget - + Printing Selector Selettore di stampa @@ -1331,166 +1302,166 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorSettingsPage - - + + Update Spoilers Aggiorna Spoiler - - + + Success Fatto - + Download URLs have been reset. Gli indirizzi di download sono stati resettati. - + Downloaded card pictures have been reset. Le immagini delle carte scaricate sono state eliminate. - + Error Errore - + One or more downloaded card pictures could not be cleared. Non è stato possibile eliminare alcune delle immagini delle carte scaricate. - + Add URL Aggiungi indirizzo - - + + URL: Indirizzo: - - + + Edit URL Modifica indirizzo: - + Network Cache Size: Dimensione cache di rete: - + Redirect Cache TTL: TTL cache dei reindirizzamenti: - + How long cached redirects for urls are valid for. Per quanto tempo sono validi i reindirizzamenti per gli URL memorizzati nella cache. - + Picture Cache Size: Dimensione cache immagini: - + Add New URL Aggiungi indirizzo URL - + Remove URL Elimina indirizzo URL - + Day(s) Giorno/i - + Updating... Aggiornando... - + Choose path Scegli il percorso - + URL Download Priority Ordine di priorità degli indirizzi - + Spoilers Spoiler - + Download Spoilers Automatically Scarica spoiler automaticamente - + Spoiler Location: Indirizzo spoiler: - + Last Change Ultima modifica - + Spoilers download automatically on launch Scarica spoiler automaticamente all'avvio - + Press the button to manually update without relaunching Premi il pulsante per aggiornare manualmente senza riavviare - + Do not close settings until manual update is complete Non chiudere le impostazioni fino a che l'aggiornamento manuale sia completato - + Download card pictures on the fly Scarica immagini delle carte in tempo reale - + How to add a custom URL Come aggiungere indirizzi personalizzati - + Delete Downloaded Images Elimina immagini scaricate - + Reset Download URLs Resetta indirizzi di download - + On-disk cache for downloaded pictures Cache su disco immagini scaricate - + In-memory cache for pictures not currently on screen Cache in memoria per immagini non attualmente su schermo @@ -1498,32 +1469,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckListHistoryManagerWidget - + Undo Annulla - + Redo Ripeti - + Undo/Redo history Annulla/Ripeti Storico - + Click on an entry to revert to that point in the history. Clicca su una riga per ritornare a quel punto nello storico delle modifiche. - + [redo] [ripeti] - + [undo] [annulla] @@ -1531,27 +1502,27 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckListModel - + Count Quantità - + Set Set - + Number Numero - + Provider ID ID Provider - + Card Carta @@ -1559,12 +1530,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckLoader - + Common deck formats (%1) Formati di mazzo comuni (%1) - + All files (*.*) Tutti i file (*.*) @@ -1661,94 +1632,94 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckPreviewWidget - + Banner Card Carta copertina - + Open in deck editor Apri nell'editor dei mazzi - + Edit Tags Modifica etichette - + Rename Deck Rinomina mazzo - + Save Deck to Clipboard Salva il mazzo negli appunti - + Annotated Con annotazioni - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated Senza annotazioni - + Not Annotated (No set info) Senza annotazioni (senza info set) - + Rename File Rinomina file - + Delete File Elimina file - + Set Banner Card Imposta carta copertina - - + + New name: Nuovo nome: - - + + Error Errore - + Rename failed Rinomina non riuscita - + Delete file Elimina file - + Are you sure you want to delete the selected file? Vuoi davvero eliminare il file selezionato? - + Delete failed Eliminazione non riuscita @@ -1781,32 +1752,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Formato impostato a %1 - + Added (%1): %2 (%3) %4 Aggiunto (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Spostato in %1 1 × "%2" (%3) - + Removed "%1" (all copies) Rimosso "%1" (tutte le copie) - + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Aggiunto - + Removed Rimosso @@ -1873,30 +1844,30 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Sideboard bloccata - - + + Error Errore - + The selected file could not be loaded. I file selezionati non posso essere caricati. - + Deck is greater than maximum file size. Il mazzo è più grande della dimensione massima del file consentita. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sicuro di voler forzare l'avvio? Ciò espellerà dalla partita tutti i giocatori che non sono pronti. - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditDeckInClipboard - + Edit deck in clipboard Modifica mazzo negli appunti - + Error Errore - + Invalid deck list. Lista del mazzo non valida. @@ -2902,17 +2873,17 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci DlgLoadDeckFromClipboard - + Load deck from clipboard Carica mazzo dagli appunti - + Error Errore - + Invalid deck list. Lista del mazzo non valida. @@ -2920,45 +2891,45 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Incolla il link a una lista di mazzo online per importarla. (Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). - - - - - + + + + + Load Deck from Website Carica Mazzo dal Sito - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Impossibile acquisire lista di mazzo da questo sito. (Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). - + Network error: %1 Errore di rete: %1 - + Received empty deck data. Ricevuta lista di mazzo vuota. - + Failed to parse deck data: %1 Impossibile acquisire i dati del mazzo: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2983,40 +2954,73 @@ https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ Carica mazzo + + DlgLocalGameOptions + + + Players: + Giocatori: + + + + General + Generale + + + + Starting life total: + Punti vita iniziali: + + + + Game setup options + Configurazione partita + + + + Remember settings + Ricorda impostazioni + + + + Local game options + Configurazione partita locale + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nome della carta (o ricerca espressioni): - + Number of hits: Numero di risultati: - + Auto play hits Riproduzione automatica dei successi - + Put top cards on stack until... Metti le carte in cima alla pila fino a... - + No cards matching the search expression exists in the card database. Proceed anyways? Non esiste alcuna carta corrispondente agli estremi di ricerca nel database. Procedere comunque? - + Cockatrice Cockatrice - + Invalid filter Filtro non valido @@ -3178,12 +3182,12 @@ La tua email verrà utilizzata per verificare il tuo account. DlgSettings - + Unknown Error loading card database Errore sconosciuto durante il caricamento del database delle carte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3200,7 +3204,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database version is too old. This can cause problems loading card information or images @@ -3217,7 +3221,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3229,7 +3233,7 @@ Per favore crea un ticket di assistenza su https://github.com/Cockatrice/Cockatr Desideri modificare l'impostazione della posizione del database? - + File Error loading your card database. Would you like to change your database location setting? @@ -3238,7 +3242,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3247,7 +3251,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3260,59 +3264,59 @@ https://github.com/Cockatrice/Cockatrice/issues Desideri modificare l'impostazione della posizione del database? - - - + + + Error Errore - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + General Generale - + Appearance Aspetto - + User Interface Interfaccia - + Card Sources Immagini carte - + Chat Chat - + Sound Suoni - + Shortcuts Scorciatoie @@ -3630,67 +3634,67 @@ Dovrai scaricare la nuova versione manualmente. DrawProbabilityWidget - + Draw Probability Probabilità di pescare - + Probability of drawing Probabilità di pescare - + Card Name Nome carta - + Type Tipo - + Subtype Sottotipo - + Mana Value Valore di mana - + At least Almeno - + Exactly Esattamente - + card(s) having drawn at least carta/e avendo pescato almeno - + cards carte - + Category Categoria - + Qty Quantità - + Odds (%) Probabilità (%) @@ -4138,143 +4142,143 @@ Dovrai scaricare la nuova versione manualmente. GeneralSettingsPage - + Reset all paths Reimposta tutti i percorsi - + All paths have been reset I percorsi sono stati resettati - - - - - - - + + + + + + + Choose path Seleziona il percorso - + Personal settings Impostazioni personali - + Language: Lingua: - + Paths (editing disabled in portable mode) Destinazioni (non si può personalizzare in modalità portatile) - + Paths Percorsi - + How to help with translations Come aiutare con le traduzioni - + Decks directory: Cartella mazzi: - + Filters directory: Cartella filtri: - + Replays directory: Cartella replay: - + Pictures directory: Cartella immagini: - + Card database: Database carte: - + Custom database directory: Cartella database personalizzata: - + Token database: Database pedine: - + Update channel Canale di aggiornamento: - + Check for client updates on startup Controlla gli aggiornamenti del client all'avvio - + Check for card database updates on startup Controlla gli aggiornamenti delle carte all'avvio - + Don't check Non controllare - + Prompt for update Chiedi se aggiornare - + Always update in the background Aggiorna sempre in background - + Check for card database updates every Controlla gli aggiornamenti delle carte ogni - + days giorni - + Notify if a feature supported by the server is missing in my client Avvisami se una funzionalità supportata dal server manca nel mio programma - + Automatically run Oracle when running a new version of Cockatrice Avvia automaticamente Oracle se Cockatrice è stato aggiornato - + Show tips on startup Mostra suggerimenti all'avvio - + Last update check on %1 (%2 days ago) Ultimo controllo %1 (%2 giorni fa) @@ -4282,47 +4286,47 @@ Dovrai scaricare la nuova versione manualmente. GraveyardMenu - + &Graveyard &Cimitero - + &View graveyard &Guarda il cimitero - + &Move graveyard to... &Muovi cimitero in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &All players &Tutti i giocatori - + &Hand &Mano - + &Exile &Esilio - + Reveal random card to... Rivela carta casuale a... @@ -4330,88 +4334,88 @@ Dovrai scaricare la nuova versione manualmente. HandMenu - + &Hand &Mano - + &View hand &Vedi mano - + Sort hand by... Ordina mano per... - + Name Nome - + Type Tipo - + Mana Value Valore di mana - + Take &mulligan (Choose hand size) Effettua un &mulligan (Scegli numero di carte) - + Take mulligan (Same hand size) Effettua un mulligan (Stessa dimensione della mano) - + Take mulligan (Hand size - 1) Effettua un mulligan (Dimensione della mano - 1) - + &Move hand to... &Sposta mano in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &Graveyard &Cimitero - + &Exile &Esilio - + &Reveal hand to... &Rivela mano a... - - + + All players Tutti i giocatori - + Reveal r&andom card to... Rivela carta c&asuale a... @@ -4419,52 +4423,52 @@ Dovrai scaricare la nuova versione manualmente. HomeWidget - + Create New Deck Crea un nuovo mazzo - + Browse Decks Esplora i mazzi - + Browse Card Database Esplora il database delle carte - + Browse EDHRec Esplora EDHRec - + Browse Archidekt Esplora Archidekt - + View Replays Guarda i replay - + Quit Esci - + Connecting... Connessione in corso... - + Connect Connetti - + Play Gioca @@ -4472,193 +4476,213 @@ Dovrai scaricare la nuova versione manualmente. LibraryMenu - + &Library &Grimorio - + &View library &Guarda il grimorio - + View &top cards of library... Guarda &le carte in cima al grimorio... - + View bottom cards of library... Guarda le carte in fondo al grimorio... - + Reveal &library to... Rive&la grimorio a... - + Lend library to... Presta il grimorio a... - + Reveal &top cards to... Rivela le &prime carte a... - + &Top of library... - &In cima al grimorio... + &Dalla cima del grimorio... - + &Bottom of library... - &In fondo al grimorio... + &Dal fondo del grimorio... - + &Always reveal top card Rivela &sempre la prima carta - + &Always look at top card &Guarda sempre la prima carta - + &Open deck in deck editor &Apri il mazzo nell'editor dei mazzi - + &Draw card &Pesca una carta - + D&raw cards... P&esca carte... - + &Undo last draw &Annulla l'ultima pescata - + Shuffle Mescola - + &Play top card &Gioca la prima carta - + Play top card &face down Gioca la prima carta a &faccia in giù - + Put top card on &bottom Metti la prima carta in fondo - + Move top card to grave&yard Metti la prima carta nel c&imitero - + Move top card to e&xile E&silia la prima carta - + Move top cards to &graveyard... Metti le prime carte nel &cimitero... - + + Move top cards to graveyard face down... + Metti le prime carte nel cimitero a faccia in giù... + + + Move top cards to &exile... &Esilia le prime carte... - + + Move top cards to exile face down... + Metti le prime carte in esilio a faccia in giù... + + + Put top cards on stack &until... Metti le carte in cima alla pila fino a... - + Shuffle top cards... Mescola le carte in cima... - + &Draw bottom card &Pesca l'ultima carta dal fondo - + D&raw bottom cards... P&esca carte dal fondo... - + &Play bottom card &Gioca la carta in fondo - + Play bottom card &face down Gioca la carta in fondo a &faccia in giù - + Move bottom card to grave&yard Metti l'ultima carta nel c&imitero - + Move bottom card to e&xile Metti l'ultima carta in esilio - + Move bottom cards to &graveyard... Metti le ultime carte nel cimitero... - + + Move bottom cards to graveyard face down... + Metti le ultime carte nel cimitero a faccia in giù... + + + Move bottom cards to &exile... &Esilia le ultime carte... - + + Move bottom cards to exile face down... + Metti le ultime carte in esilio a faccia in giù... + + + Put bottom card on &top Metti l'ultima carta in cima - + Shuffle bottom cards... Mescola le carte in fondo... - - + + &All players &Tutti i giocatori - + Reveal top cards of library Rivela le carte in cima al grimorio - + Number of cards: (max. %1) Numero di carte: (max. %1) @@ -4758,18 +4782,8 @@ Will now login. Login in corso. - - Number of players - Numero di giocatori - - - - Please enter the number of players. - Inserisci il numero di giocatori: - - - - + + Player %1 Giocatore %1 @@ -4872,8 +4886,8 @@ Login in corso. - - + + Error Errore @@ -5281,36 +5295,36 @@ La tua versione è la %1, la versione online è la %2. Mostra/Nascondi - + New Version Nuova versione - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulazioni per aver aggiornato Cockatrice alla versione %1! Oracle verrà avviato per aggiornare anche il database delle carte. - + Cockatrice installed Cockatrice installata - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulazioni per aver installato Cockatrice %1! Oracle verrà avviato per installare il database delle carte. - + Card database Database carte - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5319,29 +5333,29 @@ Vuoi provare ad aggiornare il database adesso? Se è la prima volta che usi Cockatrice o non sei sicuro, scegli "Sì" - - + + Yes - - + + No No - + Open settings Apri impostazioni - + New sets found Nuovi set trovati - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5354,17 +5368,17 @@ Codici dei set: %1 Desideri attivarli? - + View sets Vedi set - + Welcome Benvenuto - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5373,65 +5387,65 @@ Tutti i set nell'archivio delle carte sono stati abilitati. Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Gestisci Espansioni". - - + + Information Informazione - + A card database update is already running. L'aggiornamento delle carte è già in corso. - + Unable to run the card database updater: Impossibile avviare l'aggiornamento delle carte: - + Card database update running. Aggiornamento delle carte in corso. - + Failed to start. The file might be missing, or permissions might be incorrect. Avvio non riuscito. Il file potrebbe essere assente, o i permessi errati. - + The process crashed some time after starting successfully. Il processo si è arrestato in modo anomalo poco dopo essersi avviato con successo. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Richiesta scaduta. Il processo ci ha messo troppo a rispondere. L'ultima funzione waitFor...() è scaduta. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Si è verificato un errore nel cercare di scrivere al processo. Ad esempio, il processo potrebbe non essere in esecuzione, o potrebbe aver chiuso il suo canale di input. - + An error occurred when attempting to read from the process. For example, the process may not be running. Si è verificato un errore nel cercare di leggere dal processo. Ad esempio, il processo potrebbe non essere in esecuzione. - + Unknown error occurred. Si è verificato un errore sconosciuto. - + The card database updater exited with an error: %1 L'aggiornamento del database delle carte è terminato con un errore: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5442,55 +5456,55 @@ Solitamente non si dovrebbe incorrere in problemi, ma questo messaggio può sign Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - - - - - + + + + + Load sets/cards Carica set di carte - + Selected file cannot be found. Il file selezionato non può essere caricato. - + You can only import XML databases at this time. E' possibile importare solamente database XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Il nuovo set di carte è stato aggiunto correttamente. Il database delle carte verrà ricaricato. - + Sets/cards failed to import. Il nuovo set di carte non è stato importato a causa di un errore. - - - + + + Reset Password Reimposta Password - + Your password has been reset successfully, you can now log in using the new credentials. La tua password è stata reimpostata correttamente, ora puoi accedere utilizzando le nuove credenziali. - + Failed to reset user account password, please contact the server operator to reset your password. La reimpostazione della password è fallita, contatta l'amministratore del server per reimpostare la password. - + Activation request received, please check your email for an activation token. Richiesta di attivazione ricevuta, controlla il tuo indirizzo email per il codice di attivazione. @@ -5541,7 +5555,7 @@ Il database delle carte verrà ricaricato. ManaBaseWidget - + Mana Base Base di mana @@ -5695,590 +5709,610 @@ Il database delle carte verrà ricaricato. MessageLogWidget - + from play dal campo di battaglia - + from their graveyard dal suo cimitero - + from exile dall'esilio - + from their hand dalla sua mano - + the top card of %1's library la prima carta del grimorio di %1 - + the top card of their library la prima carta del suo grimorio - + from the top of %1's library dalla cima del grimorio di %1 - + from the top of their library dalla cima del suo grimorio - + the bottom card of %1's library l'ultima carta del grimorio di %1 - + the bottom card of their library l'ultima carta del suo grimorio - + from the bottom of %1's library dal fondo del grimorio di %1 - + from the bottom of their library dal fondo del suo grimorio - + from %1's library dal grimorio di %1 - + from their library dal suo grimorio - + from sideboard dalla sideboard - + from the stack dalla pila - + from custom zone '%1' dalla zona personalizzata '%1' - + %1 is now keeping the top card %2 revealed. %1 sta tenendo la prima carta %2 rivelata. - + %1 is not revealing the top card %2 any longer. %1 non sta più rivelando la prima carta %2. - + %1 can now look at top card %2 at any time. %1 può guardare la prima carta %2 della libreria in qualunque momento. - + %1 no longer can look at top card %2 at any time. %1 non può più guardare la prima carta %2 del mazzo in qualunque momento. - + %1 attaches %2 to %3's %4. %1 assegna %2 a %4 di %3. - + %1 has conceded the game. %1 ha concesso la partita. - + %1 has unconceded the game. %1 ha annullato la concessione della partita. - + %1 has restored connection to the game. %1 ha ripristinato il collegamento alla partita. - + %1 has lost connection to the game. %1 ha perso il collegamento alla partita. - + %1 points from their %2 to themselves. %1 disegna una freccia dal suo %2 a sé stesso. - + %1 points from their %2 to %3. %1 disegna una freccia dal suo %2 a %3. - + %1 points from %2's %3 to themselves. %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to %4. %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from their %2 to their %3. %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from their %2 to %3's %4. %1 disegna una freccia dal suo %2 a %4 di %3. - + %1 points from %2's %3 to their own %4. %1 disegna una freccia da %3 di %2 al suo %4. - + %1 points from %2's %3 to %4's %5. %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 creates a face down token. %1 crea una pedina a faccia in giù. - + %1 creates token: %2%3. %1 crea una pedina: %2%3. - + %1 has loaded a deck (%2). %1 ha caricato un mazzo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha caricato un mazzo con %2 carte nella sideboard (%3). - + %1 destroys %2. %1 distrugge %2. - + a card una carta - + %1 gives %2 control over %3. %1 da il controllo di %3 a %2. - + %1 puts %2 into play%3 face down. %1 mette %2 sul campo di battaglia %3 a faccia in giù. - + %1 puts %2 into play%3. %1 mette %2 sul campo di battaglia%3. - + + %1 puts %2%3 into their graveyard face down. + %1 mette %2%3 nel suo cimitero a faccia in giù. + + + %1 puts %2%3 into their graveyard. %1 mette %2 nel suo cimitero%3. + + + %1 exiles %2%3 face down. + %1 esilia %2%3 a faccia in giù. + %1 exiles %2%3. %1 esilia %2%3. - + %1 moves %2%3 to their hand. %1 mette %2 in mano%3. - + %1 puts %2%3 into their library. %1 mette %2 nel suo grimorio%3. - + %1 puts %2%3 onto the bottom of their library. %1 mette %2%3 in fondo al proprio grimorio. - + %1 puts %2%3 on top of their library. %1 mette %2 in cima al suo grimorio%3. - + %1 puts %2%3 into their library %4 cards from the top. %1 mette %2%3 nel suo grimorio %4 carte dalla cima. - + %1 moves %2%3 to sideboard. %1 mette %2 nella sideboard%3. - + + %1 plays %2%3 face down. + %1 gioca %2%3 a faccia in giù. + + + %1 plays %2%3. %1 gioca %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + %1 mette %2 nella zona personalizzata '%4'%3 a faccia in giù. + + + %1 moves %2%3 to custom zone '%4'. %1 mette %2 nella zona personalizzata '%4'%3. - + %1 tries to draw from an empty library %1 prova a pescare da un grimorio vuoto - + %1 draws %2 card(s). %1 pesca una carta.%1 pesca %2 carte.%1 pesca %2 carte. - + %1 is looking at %2. %1 sta guardando %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sta guardando %3 carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. - + bottom in fondo - + top in cima - + %1 turns %2 face-down. %1 gira %2 a faccia in giù. - + %1 turns %2 face-up. %1 gira %2 a faccia in su. - + The game has been closed. La partita è stata chiusa. - + The game has started. La partita è iniziata. - + You are flooding the game. Please wait a couple of seconds. Stai spammando la partita. Attendi un paio di secondi. - + %1 has joined the game. %1 è entrato nella partita. - + %1 is now watching the game. %1 sta osservando la partita. - + You have been kicked out of the game. Sei stato cacciato dalla partita. - + %1 has left the game (%2). %1 ha abbandonato la partita (%2). - + %1 is not watching the game any more (%2). %1 non sta più guardando la partita (%2). - + %1 is not ready to start the game any more. %1 non è più pronto a iniziare la partita. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischia il proprio mazzo e pesca una nuova mano di %2 carta.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte. - + %1 shuffles their deck and draws a new hand. %1 mischia il proprio mazzo e pesca una nuova mano. - + You are watching a replay of game #%1. Stai guardando il replay della partita #%1. - + %1 is ready to start the game. %1 è pronto a iniziare la partita. - + cards an unknown amount of cards carte - + %1 card(s) a card for singular, %1 cards for plural una carta%1 carte%1 carte - + %1 lends %2 to %3. %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 rivela %2 a %3. - + %1 reveals %2. %1 rivela %2. - + %1 randomly reveals %2%3 to %4. %1 rivela a caso %2%3 a %4. - + %1 randomly reveals %2%3. %1 rivela a caso %2%3. - + %1 peeks at face down card #%2. %1 sbircia la carta a faccia in giù #%2. - + %1 peeks at face down card #%2: %3. %1 sbircia la carta a faccia in giù #%2: %3. - + %1 reveals %2%3 to %4. %1 rivela %2%3 a %4. - + %1 reveals %2%3. %1 rivela %2%3. - + %1 reversed turn order, now it's %2. %1 ha rovesciato l'ordine dei turni, ora è %2. - + reversed invertito - + normal normale - + Heads Testa - + Tails Croce - + %1 flipped a coin. It landed as %2. %1 ha lanciato una moneta. Il risultato è %2. - + %1 rolls a %2 with a %3-sided die. %1 lancia un dado a %3 facce e ottiene %2. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. - + %1 rolls a %2-sided dice %3 times: %4. %1 lancia un dado a %2 facce %3 volte: %4. - + %1's turn. Turno di %1 - + %1 sets annotation of %2 to %3. %1 imposta le note di %2 a %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 mette %2 segnalino "%3" su %4 (totale %5).%1 mette %2 segnalini "%3" su %4 (totale %5).%1 mette %2 segnalino(i) "%3" su %4 (totale %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 toglie %2 segnalino "%3" su %4 (totale %5).%1 toglie %2 segnalini "%3" su %4 (totale %5).%1 toglie %2 segnalino(i) "%3" su %4 (totale %5). - + %1 sets counter %2 to %3 (%4%5). %1 imposta il contatore %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 imposta che %2 non STAPpi normalmente. - + %1 sets %2 to untap normally. %1 imposta che %2 STAPpi normalmente. - + %1 removes the PT of %2. %1 elimina i valori F/C di %2. - + %1 changes the PT of %2 from nothing to %4. %1 cambia F/C di %2 da vuota a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia F/C di %2 da %3 a %4. - + %1 has locked their sideboard. %1 ha bloccato la sua sideboard. - + %1 has unlocked their sideboard. %1 ha sbloccato la sua sideboard. - + %1 taps their permanents. %1 TAPpa i suoi permanenti. - + %1 untaps their permanents. %1 STAPpa i suoi permanenti. - + %1 taps %2. %1 TAPpa %2. - + %1 untaps %2. %1 STAPpa %2. - + %1 shuffles %2. %1 mescola %2. - + %1 shuffles the bottom %3 cards of %2. %1 mescola le %3 carte sul fondo da %2. - + %1 shuffles the top %3 cards of %2. %1 mescola le prime %3 carte da %2. - + %1 shuffles cards %3 - %4 of %2. %1 mescola le carte %3 - %4 da %2. - + %1 unattaches %2. %1 toglie %2. - + %1 undoes their last draw. %1 annulla la sua ultima pescata. - + %1 undoes their last draw (%2). %1 annulla la sua ultima pescata (%2). @@ -6286,110 +6320,110 @@ Il database delle carte verrà ricaricato. MessagesSettingsPage - + Word1 Word2 Word3 Parola1 Parola2 Parola3 - + Add New Message Aggiungi Nuovo Messaggio - + Edit Message Modifica Messaggio - + Remove Message Rimuovi Messaggio - + Add message Aggiungi messaggio - - + + Message: Messaggio: - + Edit message Modifica messaggio - + Chat settings Impostazioni chat - + Custom alert words Lista parole evidenziate - + Enable chat mentions Abilita menzioni in chat - + Enable mention completer Abilita completamento menzioni - + In-game message macros Messaggi rapidi in partita - + How to use in-game message macros Come usare i messaggi macro in gioco - + Ignore chat room messages sent by unregistered users Ignora i messaggi in chat inviati dagli utenti non registrati - + Ignore private messages sent by unregistered users Ignora i messaggi privati inviati dagli utenti non registrati - - + + Invert text color Inverti colore testo - + Enable desktop notifications for private messages Abilita notifiche desktop per i messaggi privati - + Enable desktop notification for mentions Abilita notifiche sul desktop per le menzioni - + Enable room message history on join Abilita messaggi recenti all'ingresso - - + + (Color is hexadecimal) (Colore in esadecimale) - + Separate words with a space, alphanumeric characters only Separare le parole con uno spazio; solo caratteri alfanumerici @@ -6402,32 +6436,37 @@ Il database delle carte verrà ricaricato. &Metti in - + &Top of library in random order - &In cima al grimorio in ordine casuale + &Cima al grimorio in ordine casuale - + X cards from the top of library... Posizione X dalla cima del grimorio... - + &Bottom of library in random order - In &fondo al grimorio in ordine casuale + &Fondo al grimorio in ordine casuale - + + T&able + Tavolo + + + &Hand &Mano - + &Graveyard &Cimitero - + &Exile &Esilio @@ -6569,57 +6608,57 @@ Il database delle carte verrà ricaricato. PhasesToolbar - + Untap step Sottofase di STAP - + Upkeep step Sottofase di mantenimento - + Draw step Sottofase di acquisizione - + First main phase Prima fase principale - + Beginning of combat step Sottofase di inizio combattimento - + Declare attackers step Sottofase di dichiarazione degli attaccanti - + Declare blockers step Sottofase di dichiarazione dei bloccanti - + Combat damage step Sottofase del danno da combattimento - + End of combat step Sottofase di fine combattimento - + Second main phase Seconda fase principale - + End of turn step Sottofase finale @@ -6636,134 +6675,138 @@ Il database delle carte verrà ricaricato. PlayerActions - + View top cards of library Guarda le carte in cima al grimorio - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Numero di carte: (max %1) - + View bottom cards of library Guarda le carte in fondo al grimorio - + Shuffle top cards of library Mescola le carte in cima al grimorio - + Shuffle bottom cards of library Mescola le carte in fondo al grimorio - + Draw hand Pesca mano - + 0 and lower are in comparison to current hand size 0 e inferiore sono relativi al numero attuale di carte in mano - + Draw cards Pesca carte + + + + + + grave + cimitero + - Move top cards to grave - Metti le prime carte nel cimitero + + + + exile + esilio - - Move top cards to exile - Esilia le prime carte + + Move top cards to %1 + - - Move bottom cards to grave - Metti le ultime care nel cimitero + + Move bottom cards to %1 + - - Move bottom cards to exile - Esilia le ultime carte - - - + Draw bottom cards Pesca le ultime carte - - + + C&reate another %1 token C&rea un'altra pedina %1 - + Create tokens Crea pedine - - + + Number: Numero: - + Place card X cards from top of library Metti la carta in posizione X dalla cima del grimorio - + Which position should this card be placed: In che posizione dovrebbe essere messa questa carta: - + (max. %1) (max %1) - + Change power/toughness Cambia forza/costituzione - + Change stats to: Cambia valori a: - + Set annotation Imposta note - + Please enter the new annotation: Inserisci le nuove note: - + Set counters Imposta i segnalini @@ -6771,48 +6814,68 @@ Il database delle carte verrà ricaricato. PlayerMenu - + Player "%1" Giocatore "%1" - + &Counters &Contatori + + + PrintingDisabledInfoWidget - - S&ay - Invi&a + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + Il selettore di stampa è disabilitato perchè hai abilitato la funzionalità di sovrascrivere le stampe selezionate con le preferenze personali per set. + +Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzichè poterle selezionare e che non vedrai le stampe scelte da altri utenti. + + + + + Enable printings again + Abilita il selettore di stampa PrintingSelector - + Display Navigation Buttons Visualizza pulsanti di navigazione + + + Printing Selector + Selettore di stampa + PrintingSelectorCardOverlayWidget - + Preference Preferenza - + Pin Printing Fissa stampa in cima alla lista - + Unpin Printing Rimuovi stampa dalla cima della lista - + Show Related cards Mostra carte correlate @@ -6861,17 +6924,25 @@ Il database delle carte verrà ricaricato. Data di rilascio - - + + Descending Discendente - + Ascending Ascendente + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + Seleziona una carta per vedere le stampe disponibili. + + PtMenu @@ -7010,6 +7081,45 @@ Il database delle carte verrà ricaricato. A .cod version of this deck already exists. Overwrite it? Una versione .cod di questo mazzo esiste già. Sovrascriverla? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + Abilitare questa funzione disattiverà il Selettore di stampa. + +Non potrai gestire le preferenze delle stampe per i singoli mazzi, o vedere le stampe scelte dagli altri giocatori per i loro mazzi. + +Dovrai usare il Gestore dei set, raggiungibile tramite Database carte -> Organizza set. + +Sicuro di voler abilitare questa funzione? + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + Disabilitare questa funzione attiverà il Selettore di stampa. + +Potrai gestire le preferenze delle stampe per i singoli mazzi nell'Editor, e configurare quale stampa viene aggiunta di default a un mazzo fissandola nel selettore. + +Potrai anche usare il Gestore dei set per personalizzare l'ordine delle stampe nel Selettore (sono disponibili anche ordinamenti predefiniti come alfabetico o per data di uscita). + +Sicuro di voler disabilitare questa funzione? + + + + Confirm Change + Conferma modifica + QPlatformTheme @@ -7158,37 +7268,37 @@ Il database delle carte verrà ricaricato. RfgMenu - + &Exile &Esilio - + &View exile &Guarda la zona di esilio - + &Move exile to... &Muovi esilio in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &Hand &Mano - + &Graveyard &Cimitero @@ -7231,6 +7341,14 @@ Il database delle carte verrà ricaricato. Partite + + SayMenu + + + S&ay + Invi&a + + SequenceEdit @@ -7295,53 +7413,53 @@ Il database delle carte verrà ricaricato. ShortcutSettingsPage - - + + Restore all default shortcuts Ripristina scorciatoie predefinite - + Do you really want to restore all default shortcuts? Sei scuro di voler ripristinare tutte le scorciatoie predefinite? - + Clear all default shortcuts Rimuovi tutte le scorciatoie predefinite - + Do you really want to clear all shortcuts? Sei sicuro di voler rimuovere tutte le scorciatoie? - + Section: Sezione: - + Action: Azione: - + Shortcut: Scorciatoia: - + How to set custom shortcuts Come impostare scorciatoie personalizzate - + Clear all shortcuts Elimina tutte le scorciatoie - + Search by shortcut name Cerca per nome della scorciatoia @@ -7410,27 +7528,27 @@ Controlla le impostazioni! SoundSettingsPage - + Enable &sounds Abilita &suoni - + Current sounds theme: Tema sonoro attuale: - + Test system sound engine Prova il funzionamento dei suoni - + Sound settings Impostazioni suoni - + Master volume Volume @@ -7646,59 +7764,123 @@ Controlla le impostazioni! TabArchidekt - - + + + Desc. - Decrescente + Decresc. - - Asc. - Crescente + + + AND + AND - - - Any Bracket - Qualsiasi fascia + + + Require ALL selected colors + Richiede TUTTI i colori selezionati - - Deck name contains... - Nome del mazzo contiene... + + + Deck name... + Nome mazzo... - - Owner name contains... - Nome del proprietario contiene... + + + Owner... + Proprietario... + + + + + Packages + Package + + + + + Advanced Filters + Filtri avanzati - Disabled - Disabilitato + Bracket: + Fascia: - + + + Any + Qualsiasi + + + + + Contains card... + Contiene carta... + + + + + Commander... + Comandante... + + + + + Tag... + Tag... + + + + + Deck Size + Dimensione mazzo + + + + Cards: + Carte: + + + + + Asc. + Cresc. + + + + Sort by: + Ordina per: + + + + Filter by: + Filtra per: + + + + Display Settings + Impostazioni di visualizzazione + + + + Search Cerca - + + Formats Formati - - Min. # of Cards: - Num. minimo di carte: - - - - Page: - Pagina: - - - + Archidekt: Archidekt: @@ -7706,60 +7888,52 @@ Controlla le impostazioni! TabDeckEditor - + Card Info Info carta - + Deck Mazzo - + Filters Filtri - + &View &Visualizza - + Card Database Database carte - + Printing Stampa - - - - - + Visible Mostra - - - - - + Floating Separata - + Reset layout Reimposta disposizione - + Deck: %1 Mazzo: %1 @@ -7767,61 +7941,55 @@ Controlla le impostazioni! TabDeckEditorVisual - + Visual Deck: %1 Mazzo visuale: %1 - + &Visual Deck Editor Editor &visuale - - + + Card Info Info carta - - + + Deck Mazzo - - + + Filters Filtri - + &View &Visualizza - + Printing Stampa - - - - + Visible Mostra - - - - + Floating Separata - + Reset layout Reimposta disposizione @@ -7886,7 +8054,7 @@ Controlla le impostazioni! - + New folder Nuova cartella @@ -7967,18 +8135,18 @@ Please enter a name: Vuoi davvero eliminare i file selezionati? - + Delete remote decks Elimina i deck remoti - + Are you sure you want to delete the selected decks? Vuoi davvero eliminare i mazzi selezionati? - + Name of new folder: Nome della nuova cartella: @@ -7996,12 +8164,12 @@ Please enter a name: Galleria mazzi - + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 @@ -8050,197 +8218,191 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Partita - - + + Player List Giocatori - - + + Card Info Info carta - - + + Messages Messaggi - - + + Replay Timeline Replay - + &Phases &Fasi - + &Game &Partita - + Next &phase Prossima &fase - + Next phase with &action Prossima sottofase + &azione - + Next &turn Prossimo &turno - + Reverse turn order Inverti l'ordine dei turni - + &Remove all local arrows &Rimuovi tutte le frecce - + Rotate View Cl&ockwise Ruota vista in senso &orario - + Rotate View Co&unterclockwise R&uota vista in senso antiorario - + Game &information &Informazioni partita - + Un&concede Rientra in gioco - - - + + + &Concede &Concedi - + &Leave game &Lascia partita - + C&lose replay C&hiudi replay - + &Focus Chat Vai alla &chat - + &Say: &Parla: - + Selected cards Carte selezionate - + &View &Visualizza - - - - + Visible Mostra - - - - + Floating Separata - + Reset layout Reimposta disposizione - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Unconcede Annulla concedi - + You have already conceded. Do you want to return to this game? Hai già concesso. Vuoi ritornare in questa partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - + A player has joined game #%1 Un giocatore si è unito alla partita #%1 - + %1 has joined the game %1 si è unito alla partita - + You have been kicked out of the game. Sei stato kickato fuori dalla partita. @@ -9343,142 +9505,152 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInterfaceSettingsPage - + General interface settings Impostazioni generali di interfaccia - + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (anzichè un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliccare gioca tutte le carte selezionate (anziché solo quella cliccata) - + &Play all nonlands onto the stack (not the battlefield) by default Gioca tutte le carte non terra nella &pila (invece che sul campo di battaglia) - + Do not delete &arrows inside of subphases Non eliminare le &freccie al cambio di sottofase - + Close card view window when last card is removed Chiudi la finestra di visualizzazione carte quando l'ultima carta viene rimossa - + Auto focus search bar when card view window is opened Passa alla barra di ricerca quando si apre la finestra di visualizzazione carta - + Annotate card text on tokens Annota il testo della carta sulle pedine - + + Show selection counter during drag selection + Mostra un contatore quando selezioni per trascinamento + + + + Show total selection counter + Mostra un contatore di elementi selezionati + + + Use tear-off menus, allowing right click menus to persist on screen Usa menù a strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo - + Notifications settings Impostazioni notifiche - + Enable notifications in taskbar Abilita notifiche nella barra delle applicazioni - + Notify in the taskbar for game events while you are spectating Notifica anche per le partite in cui si è solo uno spettatore - + Notify in the taskbar when users in your buddy list connect Notifca quando utenti nella tua lista amici si connettono - + Animation settings Impostazioni delle animazioni - + &Tap/untap animation Animazioni &TAPpa/STAPpa - + Deck editor/storage settings Impostazioni editor/archivio mazzi - + Open deck in new tab by default Apri il mazzo in una nuova scheda automaticamente - + Use visual deck storage in game lobby Usa galleria mazzi nella lobby - + Use selection animation for Visual Deck Storage Mostra animazione al passaggio del mouse nella galleria mazzi - + When adding a tag in the visual deck storage to a .txt deck: Quando aggiungi un'etichetta ad un mazzo in formato .txt nella Galleria mazzi: - + do nothing non fare nulla - + ask to convert to .cod chiedi se convertire in file .cod - + always convert to .cod converti sempre in file .cod - + Default deck editor type Editor dei mazzi predefinito - + Classic Deck Editor Editor classico - + Visual Deck Editor Editor visuale - + Replay settings Impostazioni di riproduzione - + Buffer time for backwards skip via shortcut: Tempo di attesa per saltare indietro dopo aver premuto la scorciatoia @@ -9542,24 +9714,25 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match - Modalità: Corrispondenza esatta + + Exact match + Corrispondenza esatta + + + + Includes + Include - Mode: Includes - Modalità: Include + Include / Exclude + Mode: Includes + Include / Esclude - - Mode: Include/Exclude - Modalità: Include/Esclude - - - - Filter mode (AND/OR/NOT conjunctions of filters) - Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) + + How selected and unselected colors are combined in the filter + Come vengono combinati colori selezionati e non selezionati nel filtro @@ -9585,25 +9758,108 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Inserisci il nome del file... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + Ordina per + + + + Filter by + Filtra per + + + + Save and load filters + Salva e carica filtri + + + + Filter by exact card name + Filtra per nome della carta esatto + + + + Filter by card main-type + Filtra per tipo + + + + Filter by card sub-type + Filtra per sottotipo + + + + Filter by set + Filtra per set + + + + Filter by format legality + Filtra per legalità di formato + + + + Save/Load + Salva/Carica + + + + Name + Nome + + + + Main Type + Tipo + + + + Sub Type + Sottotipo + + + + Sets + Set + + + + Formats + Formati + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + Mostra formati con almeno: + + + + cards + carte + + + Do not display formats with less than this amount of cards in the database Non mostrare i formati con meno di questo ammontare di carte nel database - + Filter mode (AND/OR/NOT conjunctions of filters) Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include @@ -9611,22 +9867,32 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + Mostra tipi con almeno: + + + + cards + carte + + + Do not display card main-types with less than this amount of cards in the database Non mostrare i tipi di carta che hanno meno occorrenze nel database di questo numero - + Filter mode (AND/OR/NOT conjunctions of filters) Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include @@ -9662,7 +9928,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtra per i set più recenti @@ -9670,19 +9936,19 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplaySetFilterWidget - + Search sets... Ricerca set... - - + + Mode: Exact Match Modalità: Corrispondenza esatta - - + + Mode: Includes Modalità: Include @@ -9690,27 +9956,37 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... Ricerca sottotipi... - + + Show sub types with at least: + Mostra sottotipi con almeno: + + + + cards + carte + + + Do not display card sub-types with less than this amount of cards in the database Non mostrare i sottotipi che hanno meno occorrenze nel database di questo numero - + Filter mode (AND/OR/NOT conjunctions of filters) Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include @@ -9724,52 +10000,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Visual Visuale - + Loading database ... Caricamento database... - + Clear all filters Elimina tutti i filtri - - Sort by: - Ordina per: - - - - Filter by: - Filtra per: - - - - Save and load filters - Salva e carica filtri - - - - Filter by exact card name - Filtra per nome della carta esatto - - - - Filter by card sub-type - Filtra per sottotipo - - - - Filter by set - Filtra per set - - - + Table Tabella @@ -9777,56 +10023,64 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckDisplayOptionsWidget - + Group by: Raggruppa per: - + Change how cards are divided into categories/groups. Modifica come le carte sono divise in categorie / gruppi. - + Sort by: Ordina per: - + Click and drag to change the sort order within the groups Clicca e trascina per modificare l'ordinamento interno ai gruppi - + Configure how cards are sorted within their groups Configura come le carte sono ordinate all'interno dei loro gruppi - - + + Toggle Layout: Overlap Disposizione: Sovrapposta - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Modifica come le carte vengono mostrate nelle zone (es. sovrapposte o completamente visibili.) - + Toggle Layout: Flat Disposizione: Piatta + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Pesca una nuova mano - + Sample hand size Dimensione della mano @@ -9834,17 +10088,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Scrivi un nome di carta qui per avere suggerimenti dal database... - + Quick search and add card Cerca e aggiungi carta al mazzo - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Cerca per la corrispondenza più prossima nel database (con auto-completamento) e premendo Invio, aggiungi la stampa preferita della carta al mazzo. @@ -9860,47 +10114,52 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageQuickSettingsWidget - + Show Folders Mostra cartelle - + Show Tag Filter Mostra filtro etichette - + + Show Color Identity + Mostra identità di colore + + + Show Tags On Deck Previews Mostra etichette sulle anteprime dei mazzi - + Show Banner Card Selection Option Mostra opzione per selezione copertina - + Draw unused Color Identities Mostra identità di colore inutilizzate - + Unused Color Identities Opacity Opacità identità di colore inutilizzate - + Deck tooltip: Suggerimento mazzo: - + None Nessuno - + Filepath Percorso file @@ -10001,133 +10260,133 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u WndSets - + Move selected set to the top Muovi set selezionato in cima - + Move selected set up Muovi set selezionato su - + Move selected set down Muovi set selezionato giù - + Move selected set to the bottom Muovi set selezionato in fondo - + Search by set name, code, or type Cerca per nome, codice o tipo del set - + Default order Ordine predefinito - + Restore original art priority order Ripristina ordine originale priorità immagini - + Enable all sets Abilita tutti i set - + Disable all sets Disabilita tutti i set - + Enable selected set(s) Abilita i set selezionati - + Disable selected set(s) Disabilita i set selezionati - + Deck Editor Editor dei mazzi - + Use CTRL+A to select all sets in the view. Usa CTRL+A per selezionare le espansioni nella visuale. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo carte in espansioni abilitate appariranno nella lista carte dell'editor di mazzo. - + Image priority is decided in the following order: La priorità di immagine è decisa nell'ordine seguente: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki innanzitutto la cartella CUSTOM (%1), poi le Espansioni abilitate in questo dialogo (da cima a fondo) - + Include cards rebalanced for Alchemy [requires restart] Includi carte ribilanciate per Alchemy [richiede riavvio] - + Card Art Immagine carta - + How to use custom card art Come usare immagini personalizzate per le carte - + Hints Suggerimenti - + Note Note - + Sorting by column allows you to find a set while not changing set priority. L'ordinamento per colonne ti permette di trovare un set senza cambiare il loro ordine di priorità. - + To enable ordering again, click the column header until this message disappears. Per riabilitare l'ordinamento, clicca sull'intestazione della colonna finché questo messaggio non scompare. - + Use the current sorting as the set priority instead Usa l'ordinamento corrente per impostare la priorità dei set - + Sorts the set priority using the same column Imposta priorità dei set usando l'ordinamento per colonna - + Manage sets Organizza set @@ -10135,72 +10394,72 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u ZoneViewWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + Ungrouped Non raggruppare - + Group by Type Raggruppa per tipo - + Group by Mana Value Raggruppa per costo - + Group by Color Raggruppa per colore - + Unsorted Non ordinato - + Sort by Name Ordina per nome - + Sort by Type Ordina per tipo - + Sort by Mana Cost Ordina per costo - + Sort by Colors Ordina per colori - + Sort by P/T Ordina per F/C - + Sort by Set Ordina per Set - + shuffle when closing Mescola alla chiusura - + pile view Raggruppa per tipo @@ -10235,7 +10494,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Deck Editor Editor dei mazzi @@ -10316,7 +10575,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Replays Replay @@ -10468,7 +10727,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Reset Layout Reimposta Disposizione @@ -10889,8 +11148,9 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - Toggle Untap - Abilita STAPpa + Toggle Skip Untapping + Toggle Untap + Salta lo STAP @@ -10909,98 +11169,102 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u + Play Card, Face Down + Gioca la carta a faccia in giù + + + Attach Card... Assegna Carta... - + Unattach Card Togli Carta - + Clone Card Clona Carta - + Create Token... Crea Pedina... - + Create All Related Tokens Crea Tutte le Pedine Correlate - + Create Another Token Crea un'Altra Pedina - + Set Annotation... Imposta Note... - + Select All Cards in Zone Seleziona Tutte le Carte nella Zona - + Select All Cards in Row Seleziona Tutte le Carte nella Riga - + Select All Cards in Column Seleziona Tutte le Carte nella Colonna - + Reveal Selected Cards to All Players Rivela Carta Selezionata a Tutti i Giocatori - - + + Bottom of Library Fondo al Grimorio - + - - + + Exile Esilio - + - + Graveyard Cimitero - + Hand Mano - - + + Top of Library Cima al Grimorio - - + Battlefield, Face Down Campo di Battaglia, faccia in giù @@ -11036,234 +11300,246 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Stack Pila - + Graveyard (Multiple) - Cimitero (multiplo) + Cimitero (Multiple) - - + + + Graveyard (Multiple), Face Down + Cimitero (Multiple), a faccia in giù + + + + Exile (Multiple) - Esilia (multiplo) + Esilia (Multiple) - + + + Exile (Multiple), Face Down + Esilio (Multiple), a faccia in giù + + + Stack Until Found Impila Fino a Trovare - + Draw Bottom Card Pesca Carta in Fondo - + Draw Multiple Cards from Bottom... Pesca Multiple Carte in Fondo - + Draw Arrow... Disegna Freccia... - + Remove Local Arrows Rimuovi Frecce Locali - + Leave Game Abbandona Partita - + Concede Concedi - + Roll Dice... Tira un Dado... - + Shuffle Library Mescola il Grimorio - + Shuffle Top Cards of Library Mescola Carte in Cima al Grimorio - + Shuffle Bottom Cards of Library Mescola Carte in Fondo al Grimorio - + Mulligan Mulligan - + Mulligan (Same hand size) Mulligan (Come la mano attuale) - + Mulligan (Hand size - 1) Mulligan (Dimensione della mano - 1) - + Draw a Card Pesca una Carta - + Draw Multiple Cards... Pesca più Carte... - + Undo Draw Annulla Pescata - + Always Reveal Top Card Rivela Sempre la Carta in Cima - + Always Look At Top Card Guarda Sempre la Carta in Cima - + Sort Hand by Name Ordina mano per Nome - + Sort Hand by Type Ordina mano per Tipo - + Sort Hand by Mana Value Ordina mano per Valore di Mana - + Reveal Hand to All Players Rivela Mano a Tutti i Giocatori - + Reveal Random Card to All Players Rivela Carta Casuale a Tutti i Giocatori - + Rotate View Clockwise Ruota Vista in Senso Orario - + Rotate View Counterclockwise Ruota Vista in Senso Antiorario - + Unfocus Text Box Togli Focus dalla Casella di Testo - + Focus Chat Vai alla chat - + Clear Chat Cancella chat - + Refresh Aggiorna - + Skip Forward Salta Avanti - + Skip Backward Salta Indietro - + Skip Forward by a lot Salta Avanti di Molto - + Skip Backward by a lot Salta Indietro di Molto - + Play/Pause Riproduci/Pausa - + Toggle Fast Forward Attiva/Disattiva Avanzamento Rapido - + Home Home - + Visual Deck Storage Galleria mazzi - + Deck Storage Archivio mazzi - + Server Server - + Account Account - + Administration Amministrazione - + Logs Registri diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index d10718a44..a53588fc1 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh (&R)更新 - + Parse Set Name and Number (if available) 可能ならセット名と番号を解析する @@ -36,61 +36,61 @@ AbstractTabDeckEditor - + Open in new tab 新しいタブで開く - + Are you sure? よろしいですか? - + The decklist has been modified. Do you want to save the changes? デッキリストが変更されています。 変更を保存しますか? - - - - - - + + + + + + Error エラー - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -132,190 +132,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error エラー - + Could not create themes directory at '%1'. '%1' にテーマディレクトリを作成できませんでした。 - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings テーマ設定 - + Current theme: 現在のテーマ - + Open themes folder テーマフォルダを開く - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings メニュー設定 - + Show keyboard shortcuts in right-click menus 右クリックメニューにキーボードショートカットを表示する - + Show game filter toolbar above list in room tab - + Card rendering カードの描画 - + Display card names on cards having a picture 画像のあるカードにカード名を表示 - + Auto-Rotate cards with sideways layout 横向きレイアウトのカードを自動回転 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector デッキに含まれるカードを印刷セレクターの一番上に上げる - + Scale cards on mouse over マウスオーバーでカードを拡大 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand スタックと手札垂直配置時の重なり率の最小値 - + Maximum initial height for card view window: カードビューウィンドウの初期高さの最大値 - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手札のレイアウト - + Display hand horizontally (wastes space) 手札を横に並べる - + Enable left justification 左揃えを有効 - + Table grid layout テーブルのレイアウト - + Invert vertical coordinate カード配置の垂直反転 - + Minimum player count for multi-column layout: プレイヤーを複数列レイアウトにする最少人数: - + Maximum font size for information displayed on cards: カードに表示する情報の最大のフォントサイズ: @@ -323,7 +301,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -643,22 +626,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 関連カードを見る - + Add card to deck デッキにカードを追加 - + Mainboard メインボード - + Sideboard サイドボード @@ -694,124 +677,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -827,133 +810,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1015,7 +998,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1023,7 +1006,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1046,32 +1029,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1079,32 +1062,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1177,17 +1160,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1310,7 +1293,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1318,166 +1301,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers スポイラーを更新 - - + + Success 完了 - + Download URLs have been reset. ダウンロードURLをリセットしました。 - + Downloaded card pictures have been reset. ダウンロードしたカード画像を削除しました。 - + Error エラー - + One or more downloaded card pictures could not be cleared. 一部のカード画像が削除できませんでした。 - + Add URL URLを追加 - - + + URL: URL: - - + + Edit URL URLを編集 - + Network Cache Size: ネットワークキャッシュサイズ: - + Redirect Cache TTL: リダイレクトキャッシュの有効期限: - + How long cached redirects for urls are valid for. キャッシュされたURLの転送の有効期間。 - + Picture Cache Size: 画像キャッシュサイズ: - + Add New URL 新しいURLを追加 - + Remove URL URLを削除 - + Day(s) - + Updating... 更新中... - + Choose path パスを選ぶ - + URL Download Priority ダウンロードURL優先設定 - + Spoilers スポイラー - + Download Spoilers Automatically スポイラーを自動的にダウンロード - + Spoiler Location: スポイラーの保存場所: - + Last Change 最新に更新 - + Spoilers download automatically on launch スポイラーは起動時に自動的にダウンロードされます。 - + Press the button to manually update without relaunching ボタンを押すと再起動せずに手動で更新します。 - + Do not close settings until manual update is complete 手動更新が完了するまでこのウィンドウを閉じないでください。 - + Download card pictures on the fly カード画像を自動的にダウンロード(英語) - + How to add a custom URL URLの追加方法について(英語) - + Delete Downloaded Images ダウンロードした画像を削除 - + Reset Download URLs ダウンロードURLをデフォルトに戻す - + On-disk cache for downloaded pictures ダウンロードした画像のディスク上キャッシュ - + In-memory cache for pictures not currently on screen 現在画面に表示されていない画像のメモリ内キャッシュ @@ -1485,32 +1468,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1518,27 +1501,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count 枚数 - + Set セット - + Number 枚数 - + Provider ID プロバイダーID - + Card カード名 @@ -1546,12 +1529,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1648,94 +1631,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1768,32 +1751,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1860,29 +1843,29 @@ This is only saved for moderators and cannot be seen by the banned person.サイドボードロック中 - - + + Error エラー - + The selected file could not be loaded. 選択したファイルが開けませんでした。 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2377,17 +2360,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2888,17 +2871,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard クリップボードからデッキを開く - + Error エラー - + Invalid deck list. 無効なデッキリストです。 @@ -2906,43 +2889,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2961,40 +2944,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ デッキを開く + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): カード名または検索条件: - + Number of hits: 枚数 - + Auto play hits 自動的にプレイ - + Put top cards on stack until... 見つかるまで上からスタックに置く... - + No cards matching the search expression exists in the card database. Proceed anyways? 条件に一致するカードはデータベースに存在しません。続行しますか? - + Cockatrice 悪魔からの助言 - + Invalid filter 不正な条件 @@ -3156,12 +3172,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database カードデータベース読み込みの不明なエラー。 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3178,7 +3194,7 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database version is too old. This can cause problems loading card information or images @@ -3195,7 +3211,7 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3208,7 +3224,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更しますか? - + File Error loading your card database. Would you like to change your database location setting? @@ -3217,7 +3233,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3226,7 +3242,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3239,59 +3255,59 @@ Would you like to change your database location setting? データベースの場所設定を変更しますか? - - - + + + Error エラー - + The path to your deck directory is invalid. Would you like to go back and set the correct path? あなたのデッキディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? あなたのカード画像ディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + Settings 設定 - + General 全般 - + Appearance 外観 - + User Interface UI - + Card Sources カードダウンロード - + Chat チャット - + Sound サウンド - + Shortcuts ショートカット @@ -3608,67 +3624,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4116,143 +4132,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths すべてのパスをリセット - + All paths have been reset すべてのパスをリセットしました - - - - - - - + + + + + + + Choose path パスを選択 - + Personal settings 個人設定 - + Language: 言語: - + Paths (editing disabled in portable mode) パス(ポータブルモードでの編集は無効) - + Paths パス - + How to help with translations - + Decks directory: デッキフォルダ: - + Filters directory: - + Replays directory: リプレイフォルダ: - + Pictures directory: カード画像フォルダ: - + Card database: カードデータベース: - + Custom database directory: カスタムデータベースフォルダ: - + Token database: トークンデータベース: - + Update channel 更新チャンネル - + Check for client updates on startup 起動時にクライアントの更新をチェック - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client サーバーでサポートされている機能がクライアントに存在しない場合に通知する - + Automatically run Oracle when running a new version of Cockatrice 新バージョンのCockatriceを起動したら自動的にOracleも起動する - + Show tips on startup 起動時にヒントを表示 - + Last update check on %1 (%2 days ago) @@ -4260,47 +4276,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4308,88 +4324,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4397,52 +4413,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4450,193 +4466,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4732,18 +4768,8 @@ Will now login. ログインします。 - - Number of players - プレイヤー人数 - - - - Please enter the number of players. - プレイヤーの人数を入れてください. - - - - + + Player %1 プレイヤー %1 @@ -4846,8 +4872,8 @@ Will now login. - - + + Error エラー @@ -5256,36 +5282,36 @@ Local version is %1, remote version is %2. 表示/非表示 - + New Version 新しいバージョン - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Cockatrice %1へのアップデートおめでとうございます! Oracleが起動し、カードデータベースが更新されます。 - + Cockatrice installed Cockatriceがインストールされました - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Cockatrice %1のインストールおめでとうございます! Oracleを起動して、初期カードデータベースをインストールします。 - + Card database カードデータベース - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5294,29 +5320,29 @@ If unsure or first time user, choose "Yes" よくわからない場合、初回起動時などは「はい」を押して下さい。 - - + + Yes はい - - + + No いいえ - + Open settings 設定を開く - + New sets found 新しいセットが見つかりました - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5325,17 +5351,17 @@ Do you want to enable it/them? 有効にしますか? - + View sets セットを見る - + Welcome ようこそ - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5344,64 +5370,64 @@ Read more about changing the set order or disabling specific sets and consequent 「セットの設定」ウィンドウ内の特定のセットを無効にしたり、順序を変更をする方法の詳細をお読みください。 - - + + Information 情報 - + A card database update is already running. データベース更新は既に起動中です。 - + Unable to run the card database updater: データベース更新が実行できませんでした: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. プロセスへの書き込み中にエラーが発生しました。プロセスが実行されていないか、入力チャネルが閉じられている等の可能性があります。 - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5411,55 +5437,55 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→クライアントの更新のチェックをクリックしてください。 - - - - - + + + + + Load sets/cards セット/カードを読み込む - + Selected file cannot be found. 選択したファイルが見つかりませんでした。 - + You can only import XML databases at this time. ここではXMLデータベースのみをインポートできます。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新しいセット/カードが正常に追加されました。 データベースが再読込されます。 - + Sets/cards failed to import. セット/カードのインポートに失敗 - - - + + + Reset Password パスワードリセット - + Your password has been reset successfully, you can now log in using the new credentials. パスワードをリセットしました。 - + Failed to reset user account password, please contact the server operator to reset your password. パスワードをリセットできませんでした。サーバー管理者にリセットしてもらってください。 - + Activation request received, please check your email for an activation token. アクティベーションリクエストを受信しました。メールでアクティベーショントークンを確認してください。 @@ -5510,7 +5536,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5664,590 +5690,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play スタックから - + from their graveyard 墓地から - + from exile 追放領域から - + from their hand 手札から - + the top card of %1's library %1のライブラリーの一番上のカード - + the top card of their library ライブラリーの一番上のカード - + from the top of %1's library %1のライブラリーの一番上から - + from the top of their library ライブラリーの一番上から - + the bottom card of %1's library %1のライブラリーの一番下のカード - + the bottom card of their library ライブラリーの一番下のカード - + from the bottom of %1's library %1のライブラリーの一番下から - + from the bottom of their library ライブラリーの一番下のカード - + from %1's library %1のライブラリーから - + from their library ライブラリーから - + from sideboard サイドボードから - + from the stack スタックから - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 は%2一番上のカードを公開した状態でゲームをプレイしている。 - + %1 is not revealing the top card %2 any longer. %1 は%2一番上のカードの公開を終えた。 - + %1 can now look at top card %2 at any time. %1はいつでも%2一番上のカードを見ることができる。 - + %1 no longer can look at top card %2 at any time. %1はもう%2一番上のカードを見ることができない。 - + %1 attaches %2 to %3's %4. %1は%3の%4に%2をつけた。 - + %1 has conceded the game. %1 が投了した!!! - + %1 has unconceded the game. %1が投了を撤回した。 - + %1 has restored connection to the game. %1 がゲームに再接続した。 - + %1 has lost connection to the game. %1 はゲームから切断された。 - + %1 points from their %2 to themselves. %1 は %2 から自分自身へ対象を指定した。 - + %1 points from their %2 to %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from %2's %3 to themselves. %1 は %2 の %3 から自分自身へ対象を指定した。 - + %1 points from %2's %3 to %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from their %2 to their %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from their %2 to %3's %4. %1 は %2 から %3 の %4 へ対象を指定した。 - + %1 points from %2's %3 to their own %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from %2's %3 to %4's %5. %1 は %2 の %3 から %4 の %5 へ対象を指定した。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 はトークン: %2 を%3作成した。 - + %1 has loaded a deck (%2). %1 はデッキをロードした。ハッシュ:%2 - + %1 has loaded a deck with %2 sideboard cards (%3). %1 はデッキをロードした。サイドボード数:%2, ハッシュ:%3 - + %1 destroys %2. %1 は %2 を取り除いた。 - + a card カード - + %1 gives %2 control over %3. %1 は %2 に %3 のコントロールを渡した。 - + %1 puts %2 into play%3 face down. %1 は %2 を%3裏向きでプレイした。 - + %1 puts %2 into play%3. %1 は %2 を%3プレイした。 - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 は %2 を%3墓地に置いた。 + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 は %2 を%3追放した。 - + %1 moves %2%3 to their hand. %1は %2 を%3手札に加えた。 - + %1 puts %2%3 into their library. %1 は %2 を%3ライブラリーに加えた。 - + %1 puts %2%3 onto the bottom of their library. %1 は %2 を%3ライブラリーの一番下に置いた。 - + %1 puts %2%3 on top of their library. %1 は %2 を%3ライブラリーの一番上に置いた。 - + %1 puts %2%3 into their library %4 cards from the top. %1 は %2 を%3ライブラリーの一番上から%4枚目に置いた。 - + %1 moves %2%3 to sideboard. %1 は %2 を%3サイドボードに置いた。 - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 は %2 を%3プレイした。 - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 は……カードがないライブラリーからカードを引こうとした! - + %1 draws %2 card(s). %1は%2枚カードを引いた。 - + %1 is looking at %2. %1 は%2を見ている。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 は%2%4%3枚のカードを見ている。 - + bottom 下から - + top 上から - + %1 turns %2 face-down. %1 は %2 を裏向きにした。 - + %1 turns %2 face-up. %1 は %2 を表向きにした。 - + The game has been closed. ゲームがクローズされました。 - + The game has started. ◇◇ゲーム開始!◇◇ - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 がゲームに参加した。 - + %1 is now watching the game. %1 が観戦に参加した。 - + You have been kicked out of the game. ゲームからキックされました。 - + %1 has left the game (%2). %1 はゲームから離脱した(%2)。 - + %1 is not watching the game any more (%2). %1 が観戦から離脱した(%2)。 - + %1 is not ready to start the game any more. %1 は準備完了を解除した。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1はライブラリーを切り直し、新たにカードを%2枚引いた。 - + %1 shuffles their deck and draws a new hand. %1はライブラリーを切り直し、新たに手札を引いた。 - + You are watching a replay of game #%1. ゲーム#%1のリプレイを再生しています。 - + %1 is ready to start the game. %1 はゲーム開始の準備が完了した!! - + cards an unknown amount of cards カード - + %1 card(s) a card for singular, %1 cards for plural %1枚のカード - + %1 lends %2 to %3. %1 は %3 に%2を貸した。 - + %1 reveals %2 to %3. %1 は %3 に%2を見せた。 - + %1 reveals %2. %1 は%2を公開した。 - + %1 randomly reveals %2%3 to %4. %1 は%3無作為に %2 を選び、%4に見せた。 - + %1 randomly reveals %2%3. %1 は%3無作為に %2 を選び、公開した。 - + %1 peeks at face down card #%2. %1 は裏向きのカード#%2の表面を確認した。 - + %1 peeks at face down card #%2: %3. %1 は裏向きのカード#%2の表面を確認した ( %3 ) 。 - + %1 reveals %2%3 to %4. %1 は%3 %2 を %4 に見せた。 - + %1 reveals %2%3. %1 は%3 %2 を公開した。 - + %1 reversed turn order, now it's %2. %1はターン順を逆にした(%2)。 - + reversed 逆転 - + normal 順転 - + Heads - + Tails - + %1 flipped a coin. It landed as %2. %1 はコインを投げた。結果は……【%2】だ! - + %1 rolls a %2 with a %3-sided die. %1 は%3面ダイスをふり、【%2】を出した。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 は%2枚のコインを投げた。表が【%3】枚、裏が【%4】枚出た。 - + %1 rolls a %2-sided dice %3 times: %4. %1 は%2面体サイコロを%3個振り、それぞれ【%4】を出した。 - + %1's turn. ■ %1 のターン■ - + %1 sets annotation of %2 to %3. %1 は %2 に注釈をつけた (%3)。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 は%2カウンターを%3に設定した (%4%5)。 - + %1 sets %2 to not untap normally. %1 は %2 をアンタップ・ステップの間にアンタップしないようにした。 - + %1 sets %2 to untap normally. %1 は %2 を通常どおりアンタップするように設定した。 - + %1 removes the PT of %2. %1 は %2 のP/Tを取り除いた。 - + %1 changes the PT of %2 from nothing to %4. %1 は %2 のP/Tを%4にした。 - + %1 changes the PT of %2 from %3 to %4. %1 は %2 のP/Tを%3から%4にした。 - + %1 has locked their sideboard. %1 はサイドボードをロックした。 - + %1 has unlocked their sideboard. %1 はサイドボードを解禁した。 - + %1 taps their permanents. %1 は自分のコントロールするパーマネントをタップした。 - + %1 untaps their permanents. %1 は自分のコントロールするパーマネントをアンタップした。 - + %1 taps %2. %1 は %2 をタップした。 - + %1 untaps %2. %1 は %2 をアンタップした。 - + %1 shuffles %2. %1 は%2を切り直した。 - + %1 shuffles the bottom %3 cards of %2. %1 は%2の一番下の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles the top %3 cards of %2. %1 は%2の一番上の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles cards %3 - %4 of %2. %1 は%2の %3 - %4 枚を無作為の順番で置いた。 - + %1 unattaches %2. %1 は %2 をはずした。 - + %1 undoes their last draw. %1 は最後に引いたカードを戻した。 - + %1 undoes their last draw (%2). %1 は最後に引いたカード ( %2 ) を戻した 。 @@ -6255,110 +6301,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 ここに入力してください - + Add New Message 定型文を追加 - + Edit Message 定型文を編集 - + Remove Message 定型文を削除 - + Add message 定型文を追加 - - + + Message: 定型文: - + Edit message 定型文を編集 - + Chat settings チャット設定 - + Custom alert words 反応するキーワード - + Enable chat mentions メンション機能を有効 - + Enable mention completer メンション補完を有効 - + In-game message macros 定型文 - + How to use in-game message macros ゲーム内メッセージマクロの使い方 - + Ignore chat room messages sent by unregistered users 未登録ユーザーのルームメッセージを無視 - + Ignore private messages sent by unregistered users 未登録ユーザーの個人チャットを無視 - - + + Invert text color 反転テキストの色 - + Enable desktop notifications for private messages 個人チャットのデスクトップ通知を有効 - + Enable desktop notification for mentions メンションのデスクトップ通知を有効 - + Enable room message history on join 参加時にルームのメッセージ履歴を表示 - - + + (Color is hexadecimal) (色は16進数) - + Separate words with a space, alphanumeric characters only 英数字のみ使用可能。単語を半角スペースで区切って下さい。 @@ -6371,32 +6417,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6538,57 +6589,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step アンタップ・ステップ - + Upkeep step アップキープ・ステップ - + Draw step ドロー・ステップ - + First main phase 戦闘前メイン・フェイズ - + Beginning of combat step 戦闘開始ステップ - + Declare attackers step 攻撃クリーチャー指定ステップ - + Declare blockers step ブロック・クリーチャー指定ステップ - + Combat damage step 戦闘ダメージ・ステップ - + End of combat step 戦闘終了ステップ - + Second main phase 戦闘後メイン・フェイズ - + End of turn step 最終フェイズ @@ -6605,134 +6656,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6740,48 +6795,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference 優先設定 - + Pin Printing ピン留めする - + Unpin Printing ピン留めを外す - + Show Related cards 関連カードを見る @@ -6830,17 +6902,25 @@ Cockatrice will now reload the card database. 提供日時順 - - + + Descending 降順 - + Ascending 昇順 + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6979,6 +7059,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7127,37 +7234,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7200,6 +7307,14 @@ Cockatrice will now reload the card database. ゲーム数 + + SayMenu + + + S&ay + + + SequenceEdit @@ -7264,53 +7379,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts すべてのショートカットを元に戻す - + Do you really want to restore all default shortcuts? 本当にすべてのショートカットキーを元に戻しますか? - + Clear all default shortcuts すべてのショートカットをクリア - + Do you really want to clear all shortcuts? 本当にすべてのショートカットキーをクリアしますか? - + Section: セクション: - + Action: アクション: - + Shortcut: ショートカット: - + How to set custom shortcuts ショートカットキーの設定方法について(英語) - + Clear all shortcuts すべてのショートカットをクリア - + Search by shortcut name ショートカット名で検索 @@ -7379,27 +7494,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds サウンドを有効 - + Current sounds theme: 現在のサウンドテーマ: - + Test system sound engine サウンドテスト - + Sound settings サウンド設定 - + Master volume 音量 @@ -7615,59 +7730,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7675,60 +7854,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info カード情報 - + Deck デッキ - + Filters フィルタ - + &View レイアウト - + Card Database - + Printing - - - - - + Visible 表示する - - - - - + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Deck: %1 デッキ: %1 @@ -7736,61 +7907,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7855,7 +8020,7 @@ Please check your shortcut settings! - + New folder 新しいフォルダーを作成する @@ -7937,18 +8102,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? 選択したデッキを削除してもよろしいですか? - + Name of new folder: 新しいフォルダの名前: @@ -7966,12 +8131,12 @@ Please enter a name: ビジュアルデッキストレージ - + Error - + Could not open deck at %1 @@ -8020,197 +8185,191 @@ Please enter a name: TabGame - - - + + + Replay リプレイ - - + + Game ゲーム - - + + Player List プレイヤーリスト - - + + Card Info カード情報 - - + + Messages メッセージ - - + + Replay Timeline リプレイタイムライン - + &Phases フェイズ - + &Game ゲーム - + Next &phase 次のフェイズ - + Next phase with &action ターン起因処理をしながら次のフェイズ - + Next &turn 次のターン - + Reverse turn order ゲームのターン順を逆転する - + &Remove all local arrows 全ての対象指定を消す - + Rotate View Cl&ockwise 席を時計回りに回転 - + Rotate View Co&unterclockwise 席を反時計回りに回転 - + Game &information ゲーム情報 - + Un&concede 投了を撤回 - - - + + + &Concede 投了する - + &Leave game ゲームから離脱する - + C&lose replay リプレイを閉じる - + &Focus Chat - + &Say: 発言欄: - + Selected cards - + &View レイアウト - - - - + Visible 表示する - - - - + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Concede 投了する - + Are you sure you want to concede this game? 本当にこのゲームを投了しますか? - + Unconcede 投了を取り消す - + You have already conceded. Do you want to return to this game? あなたはすでに投了しています! 本当にゲームに復帰しますか? - + Leave game ゲームから離脱する - + Are you sure you want to leave this game? 本当にこのゲームから離脱しますか? - + A player has joined game #%1 プレイヤーがゲーム#%1に参加した。 - + %1 has joined the game %1 がゲームに参加した - + You have been kicked out of the game. ゲームからキックされました。 @@ -9312,142 +9471,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings インターフェース全般設定 - + &Double-click cards to play them (instead of single-click) ダブルクリックでカードをプレイする (シングルクリックの代わり) - + &Clicking plays all selected cards (instead of just the clicked card) クリックしたカードだけでなく、選択したすべてのカードをプレイする。 - + &Play all nonlands onto the stack (not the battlefield) by default 土地でないカードをプレイする時、すぐに戦場に出さずにスタックに置く - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed 最後のカードが削除されたときにカード表示ウィンドウを閉じる - + Auto focus search bar when card view window is opened - + Annotate card text on tokens トークンのカードテキストを注釈にもつける - + + Show selection counter during drag selection + + + + + Show total selection counter + + + + Use tear-off menus, allowing right click menus to persist on screen ティアオフメニューを使用して、右クリックメニューを画面に表示したままにする - + Notifications settings 通知設定 - + Enable notifications in taskbar タスクバーの通知を有効 - + Notify in the taskbar for game events while you are spectating タスクバーに観戦中のゲームイベントを通知する - + Notify in the taskbar when users in your buddy list connect フレンドリストのユーザーが接続したときにタスクバーで通知する - + Animation settings アニメーション設定 - + &Tap/untap animation タップ/アンタップアニメーション - + Deck editor/storage settings デッキエディタ/ストレージ設定 - + Open deck in new tab by default デフォルトでデッキを新しいタブで開く - + Use visual deck storage in game lobby ゲームロビーでビジュアルデッキストレージを使用 - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings リプレイ設定 - + Buffer time for backwards skip via shortcut: @@ -9511,23 +9680,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9554,25 +9724,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9580,22 +9833,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9631,7 +9894,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9639,19 +9902,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9659,27 +9922,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9693,52 +9966,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9746,56 +9989,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9803,17 +10054,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter データベース内で最も近いものを検索し(自動提案付き)、Enter キーを押すと、優先印刷がデッキに追加されます。 @@ -9829,47 +10080,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9970,133 +10226,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 選択したセットを一番上へ - + Move selected set up 選択中のセットを上へ - + Move selected set down 選択中のセットを下へ - + Move selected set to the bottom 選択したセットを一番下へ - + Search by set name, code, or type セット名、略称、タイプで検索 - + Default order 初期順に戻す - + Restore original art priority order 元のアートの優先順位を復元する - + Enable all sets すべてのセットを有効 - + Disable all sets すべてのセットを無効 - + Enable selected set(s) 選択したセットを有効 - + Disable selected set(s) 選択したセットを無効 - + Deck Editor デッキエディター - + Use CTRL+A to select all sets in the view. Ctrl + A キーですべてのセットを選択します。 - + Only cards in enabled sets will appear in the card list of the deck editor. 有効にしたセットのカードのみがデッキエディタのカードリストに表示されます。 - + Image priority is decided in the following order: 画像の優先順位は、次の順序で決定されます: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 最優先はCUSTOMフォルダ(%1)、以降はこのダイアログで有効化されたセットの並び順 - + Include cards rebalanced for Alchemy [requires restart] - + Card Art カードアート - + How to use custom card art カスタムカードアートの使い方 - + Hints ヒント - + Note 注意 - + Sorting by column allows you to find a set while not changing set priority. 列でソートすると、セットの優先度設定を変えずにセットを探すことができます。 - + To enable ordering again, click the column header until this message disappears. 優先度設定を行うには、このメッセージが消えるまで同じ列見出しをクリックしてください。 - + Use the current sorting as the set priority instead 現在のソートを優先度設定として使用する - + Sorts the set priority using the same column 同じ列を使用して優先度をソートする - + Manage sets セットを管理 @@ -10104,72 +10360,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped グループなし - + Group by Type タイプ別グループ - + Group by Mana Value マナ・コスト別グループ - + Group by Color 色別グループ - + Unsorted 順不同 - + Sort by Name 名前順 - + Sort by Type タイプ順 - + Sort by Mana Cost マナ・コスト順 - + Sort by Colors 色順 - + Sort by P/T P/T順 - + Sort by Set セット順 - + shuffle when closing 閉じる時に切り直す - + pile view タイプ別に重ねて表示 @@ -10204,7 +10460,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor デッキエディター @@ -10285,7 +10541,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays リプレイ @@ -10437,7 +10693,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout レイアウトをリセット @@ -10858,7 +11114,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10878,98 +11135,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... + Unattach Card + + + + Clone Card カードをコピー - + Create Token... トークンを生成する... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library ライブラリーの一番下 - + - - + + Exile 追放領域 - + - + Graveyard 墓地 - + Hand 手札 - - + + Top of Library ライブラリーの一番上 - - + Battlefield, Face Down @@ -11005,234 +11266,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack スタック - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game ゲームから退出 - + Concede 投了 - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan マリガン - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh 更新 - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_ko.ts b/cockatrice/translations/cockatrice_ko.ts index 15237cc62..5ec1dac77 100644 --- a/cockatrice/translations/cockatrice_ko.ts +++ b/cockatrice/translations/cockatrice_ko.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 오류 - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings 테마 설정 - + Current theme: 현재 테마: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering 카드 렌더링 - + Display card names on cards having a picture 이미지가 존재하는 카드에도 카드 이름 표시 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 마우스 오버 시 카드 확대 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 손 레이아웃 - + Display hand horizontally (wastes space) 손의 카드를 가로로 정렬 - + Enable left justification 손의 카드를 좌측으로 정렬 - + Table grid layout 테이블 격자 레이아웃 - + Invert vertical coordinate 전장의 상하배치를 전환 (대지를 앞에 배치) - + Minimum player count for multi-column layout: 다열 레이아웃를 위한 최소 플레이어 인원 (4명 이상 권장) - + Maximum font size for information displayed on cards: 카드 정보에 표시할 폰트의 최대 크기: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success 성공 - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error 오류 - + One or more downloaded card pictures could not be cleared. - + Add URL URL 추가 - - + + URL: URL: - - + + Edit URL URL 수정 - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL 새 URL 추가 - + Remove URL URL 제거 - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count - + Set - + Number 카드 장수 - + Provider ID - + Card 카드 이름 @@ -1545,12 +1528,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ This is only saved for moderators and cannot be seen by the banned person.사이드보드 카드 교체 불가능 - - + + Error 오류 - + The selected file could not be loaded. 지정하신 파일을 불러올 수 없었습니다. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 클립보드에서 덱 가져오기 - + Error 오류 - + Invalid deck list. 클립보드의 덱 리스트를 읽어올 수 없습니다. @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ 덱 불러오기 + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 데이터베이스를 불러올 때 미상의 오류가 발생하였습니다. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3173,7 +3189,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database version is too old. This can cause problems loading card information or images @@ -3190,7 +3206,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3199,7 +3215,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3208,7 +3224,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3217,7 +3233,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3226,61 +3242,61 @@ Would you like to change your database location setting? - - - + + + Error 오류 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 덱 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 카드 이미지 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + Settings 환경설정 - + General 일반 - + Appearance 외형 - + User Interface 인터페이스 - + Card Sources - + Chat 대화 - + Sound 소리 - + Shortcuts 단축키 @@ -3594,67 +3610,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4102,143 +4118,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path 경로 선택 - + Personal settings 개인 설정 - + Language: 언어: - + Paths (editing disabled in portable mode) - + Paths 디렉토리 경로 - + How to help with translations - + Decks directory: 덱 파일 경로: - + Filters directory: - + Replays directory: 리플레이 파일 경로: - + Pictures directory: 카드 이미지 경로: - + Card database: 카드 데이터베이스 경로: - + Custom database directory: - + Token database: 토큰 데이터베이스 경로: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 서버에서 서포트하는 기능이 클라이언트에 존재하지 않을 경우 알림 - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4246,47 +4262,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4294,88 +4310,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4383,52 +4399,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4436,193 +4452,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4718,18 +4754,8 @@ Will now login. 로그인합니다. - - Number of players - 플레이어 인원 - - - - Please enter the number of players. - 최대 플레이어 인원을 입력해 주세요. - - - - + + Player %1 플레이어 %1 @@ -4832,8 +4858,8 @@ Will now login. - - + + Error 오류 @@ -5243,144 +5269,144 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database 카드 데이터베이스 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No 아니오 - + Open settings 설정 열기 - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets 셋 보기 - + Welcome 환영합니다 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information 알림 - + A card database update is already running. 이미 카드 데이터베이스 업데이트가 진행 중입니다. - + Unable to run the card database updater: 카드 데이터베이스 업데이트를 진행할 수 없습니다: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5388,54 +5414,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5486,7 +5512,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5640,590 +5666,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 전장에 - + from their graveyard - + from exile 추방 영역에 - + from their hand 자신의 손에 - + the top card of %1's library %1의 서고 맨 위의 카드 - + the top card of their library - + from the top of %1's library %1의 서고 맨 위에 - + from the top of their library - + the bottom card of %1's library %1의 서고 맨 밑의 카드 - + the bottom card of their library - + from the bottom of %1's library %1의 서고 맨 밑에 - + from the bottom of their library - + from %1's library %1의 서고에 - + from their library - + from sideboard 사이드보드에 - + from the stack 스택에 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1이(가) %2 맨 위 카드를 공개합니다. - + %1 is not revealing the top card %2 any longer. %1이(가) %2 맨 위 카드를 더 이상 공개하지 않습니다. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1님이 자신의 %2에서 자신의 %3을(를) 가리켰습니다. - + %1 points from their %2 to %3's %4. %1님이 자신의 %2에서 %3님의 %4을(를) 가리켰습니다. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1님이 토큰 생성: %2%3. - + %1 has loaded a deck (%2). %1이(가) 덱을 불러왔습니다. (해시값: %2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1이(가) %2장의 사이드보드가 있는 덱을 불러왔습니다. (해시값: %3) - + %1 destroys %2. - + a card 카드 한 장 - + %1 gives %2 control over %3. %1이(가) %3의 조종권을 %2에게 넘깁니다. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1이(가) %3서 %2을(를) 전장에 놓았습니다. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1이(가) %3 있던 %2을(를) 추방합니다. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1이(가) %3 있던 %2을(를) 서고 맨 밑에 넣었습니다. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1이(가) %3서 %2을(를) 플레이 하였습니다. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. 게임이 종료되었습니다. - + The game has started. 게임이 시작되었습니다. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1님이 게임에 입장했습니다. - + %1 is now watching the game. %1이(가) 관전을 시작하였습니다. - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. %1님이 게임 시작 준비를 끝냈습니다. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. %1님이 %2을(를) 공개. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads 앞면 - + Tails 뒷면 - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1의 차례입니다. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. %1님이 자신의 지속물을 탭. - + %1 untaps their permanents. %1님이 자신의 지속물을 언탭. - + %1 taps %2. %1님이 %2을(를) 탭. - + %1 untaps %2. %1님이 %2을(를) 언탭. - + %1 shuffles %2. %1님이 %2를 셔플했습니다. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1님이 %2을(를) 뗴어냈습니다. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6231,110 +6277,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message 메세지 추가 - - + + Message: 메세지: - + Edit message - + Chat settings 대화 설정 - + Custom alert words 키워드 알림 - + Enable chat mentions 대화 중 본인의 사용자명 멘션 시 해당 문장을 강조 - + Enable mention completer 멘션 자동완성 기능 - + In-game message macros 게임 내 대화 매크로 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 서버에 가입하지 않은 사용자의 채널 대화 차단 - + Ignore private messages sent by unregistered users 서버에 가입하지 않은 사용자가 보낸 1:1 대화 차단 - - + + Invert text color 문장 색 반전 - + Enable desktop notifications for private messages 1:1 대화를 받을 시 데스크탑 알림 설정 - + Enable desktop notification for mentions 자신의 사용자명 멘션 시 데스크탑 알림 설정 - + Enable room message history on join 채널 입장 시 이전 대화 기록 표시 - - + + (Color is hexadecimal) (16진수 색상 코드) - + Separate words with a space, alphanumeric characters only 각 단어마다 스페이스바로 띄어써주세요. 문자 및 숫자만 가능합니다. @@ -6347,32 +6393,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6514,57 +6565,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step 언탭단 - + Upkeep step 유지단 - + Draw step 뽑기단 - + First main phase 첫번째 본단계 - + Beginning of combat step 전투 시작단 - + Declare attackers step 공격자 선언단 - + Declare blockers step 방어자 선언단 - + Combat damage step 전투 피해단 - + End of combat step 전투 종료단 - + Second main phase 두번째 본단계 - + End of turn step 종료단 @@ -6581,134 +6632,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6716,48 +6771,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6806,17 +6878,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6955,6 +7035,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7103,37 +7210,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7176,6 +7283,14 @@ Cockatrice will now reload the card database. 게임 + + SayMenu + + + S&ay + + + SequenceEdit @@ -7240,53 +7355,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7353,27 +7468,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 음향 효과 켜기 - + Current sounds theme: 현재 소리 테마: - + Test system sound engine 시스템 사운드 엔진 테스트 - + Sound settings 음향 설정 - + Master volume 주 음량 @@ -7589,59 +7704,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7649,60 +7828,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 카드 정보 - + Deck - + Filters 카드 필터 - + &View &보기 - + Card Database - + Printing - - - - - + Visible 표시 - - - - - + Floating 띄우기 - + Reset layout 화면 레이아웃 초기화 - + Deck: %1 덱 편집기: %1 @@ -7710,61 +7881,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7829,7 +7994,7 @@ Please check your shortcut settings! - + New folder 새 폴더 @@ -7911,18 +8076,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: 새 폴더의 이름: @@ -7940,12 +8105,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7994,197 +8159,191 @@ Please enter a name: TabGame - - - + + + Replay 리플레이 - - + + Game 게임 - - + + Player List 플레이어 목록 - - + + Card Info 카드 정보 - - + + Messages 메세지 - - + + Replay Timeline 리플레이 타임라인 - + &Phases 단계 - + &Game 게임 - + Next &phase 다음 단계로 진행 - + Next phase with &action - + Next &turn 턴 넘기기 - + Reverse turn order - + &Remove all local arrows 내가 그린 화살표 제거 - + Rotate View Cl&ockwise 플레이어 위치 시계방향으로 조정 - + Rotate View Co&unterclockwise 플레이어 위치 반시계방향으로 조정 - + Game &information 게임 정보 - + Un&concede - - - + + + &Concede 항복 - + &Leave game 게임 나가기 - + C&lose replay 리플레이 닫기 - + &Focus Chat - + &Say: 말하기: - + Selected cards - + &View &보기 - - - - + Visible 표시 - - - - + Floating 띄우기 - + Reset layout 레이아웃 초기화 - + Concede 항복 - + Are you sure you want to concede this game? 정말 게임에서 항복하시겠습니까? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game 게임 나가기 - + Are you sure you want to leave this game? 정말 게임에서 나가시겠습니까? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. @@ -9282,142 +9441,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 일반 인터페이스 설정 - + &Double-click cards to play them (instead of single-click) 카드를 더블 클릭해서 발동 (해제시 한번만 클릭하면 발동 됨) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 모든 대지가 아닌 카드를 발동 시에 스택으로 이동 (해제시 전장으로 바로 이동) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 토큰에 카드 텍스트 주석 자동 추가 - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - 상태 표시줄 알림 설정 - - - - Notify in the taskbar for game events while you are spectating - 관전중인 게임의 상태 표시줄 알림 설정 - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - 애니메이션 설정 - - - - &Tap/untap animation - 탭/언탭 애니메이션 - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + Enable notifications in taskbar + 상태 표시줄 알림 설정 + - do nothing + Notify in the taskbar for game events while you are spectating + 관전중인 게임의 상태 표시줄 알림 설정 + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + 애니메이션 설정 + + + + &Tap/untap animation + 탭/언탭 애니메이션 - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9481,23 +9650,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9524,25 +9694,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9550,22 +9803,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9601,7 +9864,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9609,19 +9872,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9629,27 +9892,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9663,52 +9936,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9716,56 +9959,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9773,17 +10024,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9799,47 +10050,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9940,133 +10196,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 선택한 확장판을 맨 위로 올리기 - + Move selected set up 선택한 확장판을 한 단계 올리기 - + Move selected set down 선택한 확장판을 한 단계 내리기 - + Move selected set to the bottom 선택한 확장판을 맨 아래로 내리기 - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets 모든 확장판 활성화 - + Disable all sets 모든 확장판 비활성화 - + Enable selected set(s) 선택한 세트 적용 - + Disable selected set(s) 선택한 세트 미적용 - + Deck Editor 덱 편집기 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 카드 이미지 - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10074,72 +10330,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 닫은 후 덱 섞기 - + pile view 카드 유형별 행 정렬 @@ -10174,7 +10430,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor 덱 편집기 @@ -10255,7 +10511,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10407,7 +10663,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10828,7 +11084,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10848,98 +11105,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile 추방 영역 - + - + Graveyard 무덤 - + Hand - - + + Top of Library - - + Battlefield, Face Down @@ -10975,234 +11236,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede 항복 - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 멀리건 - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index d42347f0b..773b042d2 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Verversen - + Parse Set Name and Number (if available) Interpreteer Setnaam en Nummer (indien beschikbaar) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Open in nieuw tabblad - + Are you sure? Weet je het zeker? - + The decklist has been modified. Do you want to save the changes? De decklijst is gewijzigd. Wil je de wijzigingen opslaan? - - - - - - + + + + + + Error Foutmelding - + Could not open deck at %1 Kon het deckbestand niet openen: %1 - + Could not save remote deck Kon remote deck niet opslaan - - + + The deck could not be saved. Please check that the directory is writable and try again. Het deck kon niet opgeslagen worden. Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. - + Save deck Deck opslaan - + The deck could not be saved. Het deck kon niet opslagen worden. - + There are no cards in your deck to be exported Er zijn geen kaarten in je deck om te exporteren. @@ -101,7 +101,7 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. Add Analytics Panel - + Analyse Paneel Toevoegen @@ -133,202 +133,168 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. AppearanceSettingsPage - + seconds secondes - + Error Foutmelding - + Could not create themes directory at '%1'. Kon de themes map niet aanmaken op '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Als je deze functie aan zet, wordt het gebruik van de drukselector uitgezet. - -Je kan dan geen drukvoorkeuren per deck beheren en je kan niet zien welke drukken anderen voor hun decks hebben geselecteerd. - -Je moet dan de Set Manager gebruiken, die beschikbaar is via Card Database -> Manage Sets. - -Weet je zeker dat je deze functie wil aanzetten? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Als je deze functie uit zet, wordt de drukselector aan gezet. - -Je kunt nu per deck drukken kiezen in de Deck Editor en instellen welke druk standaard aan een deck wordt toegevoegd door deze vast te zetten in de drukselector. - -Je kunt ook de Set Manager gebruiken om de aangepaste sorteervolgorde voor drukken in de drukselector aan te passen (er zijn ook andere sorteervolgordes beschikbaar, zoals alfabetisch of op datum van uitgave). - -Weet je zeker dat je deze functie wilt uitschakelen? - - - - Confirm Change - Bevestig Wijziging - - - + Theme settings Thema opties - + Current theme: Huidige thema: - + Open themes folder - Open themafolder + Themasmap Openen - + Home tab background source: Starttabblad achtergrond bron: - + Home tab background shuffle frequency: Starttabblad achtergrond verversperiode. - + Disabled Uitgeschakeld - + + Display card name of background in bottom right: + Kaartnaam van achtergrond rechtsonder weergeven + + + Menu settings Menu instellingen - + Show keyboard shortcuts in right-click menus Laat snelkoppelingen in rechtermuisknop menus zien - + Show game filter toolbar above list in room tab - + Spelfilter werkbalk weergeven boven lijst in kamertab - + Card rendering Kaartweergave - + Display card names on cards having a picture Kaartnamen altijd weergeven op kaarten met een afbeelding - + Auto-Rotate cards with sideways layout - Draai kaarten samen met zijwaardse layout + Zijwaardse kaarten gedraaid weergeven - + Override all card art with personal set preference (Pre-ProviderID change behavior) Overschrijf alle kaart beelden met eigen set voorkeuren (Gedrag van voor implementatie van ProviderID) - + Bump sets that the deck contains cards from to the top in the printing selector Plaats sets van andere kaarten in het deck bovenin de drukselector - + Scale cards on mouse over Kaarten schalen met de muis - + Use rounded card corners Ronde kaarthoekjes - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage van kaarten op de stack en in verticale handweergave - + Maximum initial height for card view window: Grootste begingrootte voor kaartbekijkvenster - - + + rows rijen - + Maximum expanded height for card view window: Grootste uitgebreide grootte voor kaartbekijkvenster - + Card counters Kaart-counters - + Counter %1 Counter %1 - + Hand layout Handweergave - + Display hand horizontally (wastes space) Hand horizontaal weergeven (gebruikt extra ruimte) - + Enable left justification Linker justificatie inschakelen - + Table grid layout Tafelindeling - + Invert vertical coordinate Verticale coördinaat omkeren - + Minimum player count for multi-column layout: Minimaal aantal spelers voor kolommenindeling: - + Maximum font size for information displayed on cards: Maximale lettergrootte voor informatie die op kaarten wordt weergegeven: @@ -336,9 +302,14 @@ Weet je zeker dat je deze functie wilt uitschakelen? ArchidektApiResponseDeckDisplayWidget - + + Back to results + Terug naar resultaten + + + Open Deck in Deck Editor - + Open Deck in Deckbewerker @@ -562,7 +533,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Name (Exact) - + Naam (Exact) @@ -656,22 +627,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoPictureWidget - + View related cards Bekijk gerelateerde kaarten - + Add card to deck Voeg kaart aan deck toe - + Mainboard Mainboard - + Sideboard Sideboard @@ -686,12 +657,12 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Set: - + Set: Collector Number: - + Verzamelnummer: @@ -707,124 +678,124 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardMenu - + Re&veal to... &Onthul aan.... - + &All players &Alle spelers - + View related cards Bekijk gerelateerde kaarten - + Token: Token: - + All tokens Alle tokens - + &Select All &Selecteer Alle - + S&elect Row S&electeer Rij - + S&elect Column S&electeer Kolom - + &Play &Speel - + &Hide &Verberg - + Play &Face Down Speel kaart &verdekt - + &Tap / Untap Turn sideways or back again &Tap / Ontap - - Toggle &normal untapping - &Normal ontappen schakelen + + Skip &untapping + &Ontappen overslaan - + T&urn Over Turn face up/face down &Draai om - + &Peek at card face &Gluren naar de voorkant van kaart - + &Clone &Kloneer - + Attac&h to card... Aan de kaart &bevestigen.... - + Unattac&h &Losmaken - + &Draw arrow... &Teken pijl.... - + &Set annotation... Aantekeningen &zetten.... - + Ca&rd counters Kaart-&counters - + &Add counter (%1) Voeg counter &toe (%1) - + &Remove counter (%1) &Verwijder counter (%1) - + &Set counters (%1)... Counters &instellen (%1)... @@ -840,133 +811,133 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardZoneLogic - + their hand nominative hun hand - + %1's hand nominative %1s hand - + their library look at zone hun library - + %1's library look at zone %1s library - + of their library top cards of zone, van hun library - + of %1's library top cards of zone van %1s library - + their library reveal zone hun library - + %1's library reveal zone %1s library - + their library shuffle hun library - + %1's library shuffle %1s library - - - their library - nominative - hun library - - - - %1's library - nominative - %1s library - + their library + nominative + hun library + + + + %1's library + nominative + %1s library + + + their graveyard nominative hun kerkhof - + %1's graveyard nominative %1s kerkhof - + their exile nominative hun verbannen - + %1's exile nominative %1s verbannen - - - their sideboard - look at zone - hun sideboard - - - - %1's sideboard - look at zone - %1s sideboard - their sideboard - nominative + look at zone hun sideboard %1's sideboard + look at zone + %1s sideboard + + + + their sideboard + nominative + hun sideboard + + + + %1's sideboard nominative %1s sideboard - + their custom zone '%1' nominative hun aangepaste zone '%1' - + %1's custom zone '%2' nominative %1s aangepaste zone '%2' @@ -1007,36 +978,36 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Add Panel - + Paneel Toevoegen Remove Panel - + Paneel Verwijderen Save Layout - + Indeling Opslaan Load Layout - + Indeling Laden DeckEditorCardDatabaseDockWidget - + Card Database - + Kaartdatabase DeckEditorCardInfoDockWidget - + Card Info Kaartinfo @@ -1059,32 +1030,32 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Toevoegen aan sideboard - + Select Printing Selecteer Druk - + Show on EDHRec (Commander) Bekijk op EDHRec (Commander) - + Show on EDHRec (Card) Bekijk op EDHRec (Kaart) - + Show Related cards Gerelateerde kaarten tonen - + Add card to &maindeck Kaart aan deck &toevoegen - + Add card to &sideboard Kaart aan &sideboard toevoegen @@ -1092,32 +1063,32 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorDeckDockWidget - + Loading Database... - + Database Laden... - + Banner Card Vaandelkaart - + Main Type Hoofdtype - + Mana Cost Mana Kosten - + Colors Kleuren - + Select Printing Selecteer Druk @@ -1159,7 +1130,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Format: - + Format: @@ -1190,17 +1161,17 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Wis alle filters - + Delete selected Selectie verwijderen @@ -1210,7 +1181,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v &Deck Editor - &Deck Editor + &Deckbewerker @@ -1323,7 +1294,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorPrintingSelectorDockWidget - + Printing Selector Drukselector @@ -1331,227 +1302,227 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Succes - + Download URLs have been reset. Download URL's zijn gereset. - + Downloaded card pictures have been reset. Gedownloade kaartafbeeldingen's zijn gereset. - + Error Fout - + One or more downloaded card pictures could not be cleared. Een of meer gedownloade kaartafbeeldingen konden niet worden gewist. - + Add URL URL toevoegen - - + + URL: URL: - - + + Edit URL URL bewerken - + Network Cache Size: Netwerk Cache Grootte: - + Redirect Cache TTL: Redirect Cache TTL: - + How long cached redirects for urls are valid for. Hoe lang cached redirects voor urls geldig zijn - + Picture Cache Size: Afbeeldingen Cache Grootte: - + Add New URL Nieuwe URL toevoegen - + Remove URL URL verwijderen - + Day(s) Dag(en) - + Updating... Bijwerken... - + Choose path Kies locatie - + URL Download Priority URL Download Prioriteit - + Spoilers Spoilers - + Download Spoilers Automatically Spoilers Automatisch Downloaden - + Spoiler Location: Spoiler Locatie: - + Last Change Laatste Wijziging - + Spoilers download automatically on launch Spoilers downloaden automatisch bij opstarten - + Press the button to manually update without relaunching Druk op de knop om handmatig bij te werken zonder opnieuw te starten. - + Do not close settings until manual update is complete Sluit de instellingen niet af voordat de handmatige update is voltooid - + Download card pictures on the fly Kaartafbeeldingen on the fly downloaden - + How to add a custom URL Hoe een aangepaste URL toe te voegen - + Delete Downloaded Images Gedownloade Afbeeldingen Verwijderen - + Reset Download URLs Reset Download URL's - + On-disk cache for downloaded pictures Cache op schijf voor gedownloade afbeeldingen - + In-memory cache for pictures not currently on screen Cache in geheugen voor afbeeldingen die momenteel niet op het scherm staan DeckListHistoryManagerWidget - - - Undo - - - Redo - + Undo + Ontdoen - Undo/Redo history - + Redo + Herdoen + Undo/Redo history + Ontdoe/Herdoe geschiedenis + + + Click on an entry to revert to that point in the history. - + Klik een regel om terug te gaan naar dat punt in de geschiedenis. - + [redo] - + [herdoen] - + [undo] - + [ontdoen] DeckListModel - + Count Hoeveelheid - + Set Set - + Number Nummer - + Provider ID Provider ID - + Card Kaart @@ -1559,12 +1530,12 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckLoader - + Common deck formats (%1) Algemene deckformaten (%1) - + All files (*.*) Alle bestanden (*.*) @@ -1661,94 +1632,94 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckPreviewWidget - + Banner Card Vaandelkaart - + Open in deck editor In deckbewerker openen - + Edit Tags Tags Aanpassen - + Rename Deck Deck Hernoemen - + Save Deck to Clipboard Bewaar Deck op Klembord - + Annotated Geannoteerd - + Annotated (No set info) Geannoteerd (Geen set info) - + Not Annotated Niet Geannoteerd - + Not Annotated (No set info) Niet Geannoteerd (Geen set info) - + Rename File Bestand Hernoemen - + Delete File Bestand Verwijderen - + Set Banner Card Vaandelkaart Instellen - - + + New name: Nieuwe naam: - - + + Error Foutmelding - + Rename failed Hernoemen mislukt - + Delete file Bestand Verwijderen - + Are you sure you want to delete the selected file? Weet je zeker dat je het opgeslagen bestand wilt verwijderen? - + Delete failed Verwijderen mislukt @@ -1758,57 +1729,57 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Rename deck to "%1" from "%2" - + Hernoem deck naar "%1" van "%2" Updated comments (was %1 chars, now %2 chars) - + Opmerkingen bijgewerkt (van %1 karakters naar %2 karakters) Set banner card to %1 (%2) - + Vaandelkaart veranderd naar %1 (%2) Tags changed - + Tags veranderd Set format to %1 - + Format veranderd naar %1 - + Added (%1): %2 (%3) %4 - + Toegevoegd (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Verplaatst naar %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + Verwijderd "%1" (alle exemplaren) - + %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) - + Added - + Toegevoegd - + Removed - + Verwijderd @@ -1873,30 +1844,30 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Sideboard vergrendeld - - + + Error Fout - + The selected file could not be loaded. Het geselecteerde bestand kon niet geladen worden. - + Deck is greater than maximum file size. Deck is groter dan maximum bestandsgrootte. - + Are you sure you want to force start? This will kick all non-ready players from the game. Weet je zeker dat je geforceerd beginnen wil? Dit zal alle spelers die niet gereed zijn van het spel verwijderen. - + Cockatrice Cockatrice @@ -2107,17 +2078,17 @@ Wil je het deck omzetten naar het .cod-formaat? Only &buddies can join - Alleen &vrienden kunnen meedoen + Alleen &maatjes kunnen aansluiten Only &registered users can join - Alleen ge&registreerde gebruikers kunnen meedoen + Alleen ge&registreerde gebruikers kunnen aansluiten Joining restrictions - Restricties voor meedoen + Restricties voor aansluiten @@ -2162,7 +2133,7 @@ Wil je het deck omzetten naar het .cod-formaat? Create game as judge - + Maak spel aan als judge @@ -2391,17 +2362,17 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditDeckInClipboard - + Edit deck in clipboard Deck in klembord aanpassen... - + Error Foutmelding - + Invalid deck list. Niet geldige deck lijst. @@ -2604,7 +2575,7 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo Hide 'buddies only' games - Verberg 'alleen vrienden' spellen + Verberg 'alleen maatjes' spellen @@ -2630,7 +2601,7 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo Hide games not created by buddies Hide games not created by buddy - + Verberg spellen niet aangemaakt door maatjes @@ -2902,17 +2873,17 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgLoadDeckFromClipboard - + Load deck from clipboard Laad dek van klembord - + Error Fout - + Invalid deck list. Niet geldige deck lijst. @@ -2920,45 +2891,45 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Plak hier een link naar een decklijst site om deze te importeren. (Archidekt, Deckstats, Moxfield, en TappedOut worden ondersteund.) - - - - - + + + + + Load Deck from Website Deck van Website Laden. - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Geen lezer beschikbaar voor deze deck-leveraar. (Archidekt, Deckstats, Moxfield, en TappedOut worden ondersteund.) - + Network error: %1 Netwerk fout: %1. - + Received empty deck data. - + Lege deckinformatie ontvangen. - + Failed to parse deck data: %1 - + Parsen deckinformatie mislukt: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2966,7 +2937,13 @@ https://archidekt.com/decks/9999999 https://deckstats.net/decks/99999/9999999-your-deck-name/en https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz https://tappedout.net/mtg-decks/your-deck-name/ - + De opgegeven URL is niet herkend als geldige deck URL. +Geldige deck URLs zien er zo uit: + +https://archidekt.com/decks/9999999 +https://deckstats.net/decks/99999/9999999-your-deck-name/en +https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz +https://tappedout.net/mtg-decks/your-deck-name/ @@ -2977,40 +2954,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Laad dek + + DlgLocalGameOptions + + + Players: + Spelers: + + + + General + Algemeen + + + + Starting life total: + Beginnend levenstotaal: + + + + Game setup options + Spel opzet opties + + + + Remember settings + Instellingen onthouden + + + + Local game options + Opties plaatselijk spel + + DlgMoveTopCardsUntil - + Card name (or search expressions): Kaart naam (of zoekexpressies) - + Number of hits: Hoeveelheid resultaten: - + Auto play hits Speel resultaten automatisch - + Put top cards on stack until... Plaats bovenste kaart op stack totdat... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Er zijn geen kaarten die bij deze zoekopdracht passen in de kaartendatabase. Toch doorgaan? - + Cockatrice Cockatrice - + Invalid filter Ongeldige filter @@ -3131,53 +3141,53 @@ Je email zal gebruikt worden om je account te controleren. Unmodified Cards: - + Onaangepaste Kaarten: Modified Cards: - + Aangepaste Kaarten: Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Vink Sets aan om deze in te schakelen. Versleep om ze te herordenen en hun prioriteit te veranderen. Kaarten zullen de druk gebruiken van de set met de hoogste prioriteit. Clear all set information - + Wis alle set informatie Set all to preferred - + Alle naar voorkeur instellen Bulk modified printings. - + Drukken aangepast als groep Cleared all printing information. - + Alle drukinformatie gewist Set all printings to preferred. - + Alle drukken naar voorkeur instellen DlgSettings - + Unknown Error loading card database Onbekende Fout in het lezen van de kaart database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3194,7 +3204,7 @@ U zou oracle moeten herstarten om uw database te updaten. Zou u de locatie van uw database willen veranderen? - + Your card database version is too old. This can cause problems loading card information or images @@ -3211,7 +3221,7 @@ Dit zou mogelijk opgelost worden door oracle opnieuw te starten en je kaart data Wil je de locatie van je database veranderen? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3224,7 +3234,7 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues met uw card Wilt u uw database lokatie aanpassen? - + File Error loading your card database. Would you like to change your database location setting? @@ -3233,7 +3243,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3242,7 +3252,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3255,59 +3265,59 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues asltublieft Wilt u uw database lokatie aanpassen? - - - + + + Error Fout - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Het pad naar uw deck map is niet geldig. Wilt u terug gaan en de correcte pad invullen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Het pad naar uw kaart afbeelding map is niet geldig. Wil je terug gaan en het juiste pad invullen? - + Settings Instellingen - + General Algemeen - + Appearance Weergave - + User Interface Gebruikersomgeving - + Card Sources Kaart Bronnen - + Chat Chat - + Sound Geluid - + Shortcuts Sneltoetsen @@ -3317,39 +3327,41 @@ Wilt u uw database lokatie aanpassen? Card Update Check - + Kaartbewerkingscontrole It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Het is meer dan %2 dagen geleden sinds je het laatst je kaartendatabase hebt gecontroleerd op bewerkingen. +Kies hoe je de kaartendatabasebewerker wil uitvoeren. +Je kan altijd dit gedrag aanpassen in de "Algemeen" instellingtab. Run in foreground - + In voorgrond uitvoeren Run in background - + In achtergrond uitvoeren Run in background and always from now on - + Altijd in achtergrond uitvoeren vanaf nu Don't prompt again and don't run - + Niet opniew vragen en niet uitvoeren Don't run this time - + Niet nu uitvoeren @@ -3415,7 +3427,7 @@ Ga naar de downloadpagina om handmatig bij te werken. Downloading update: %1 - + Bewerkingen downloaden: %1 @@ -3491,12 +3503,13 @@ Ga naar de downloadpagina om handmatig bij te werken. Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Helaas, de automatische bewerker is niet geslaagd in het vinden van een toepasselijke download. +Je moet misschien handmatig de nieuwe versie downloaden. Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - + Controleer alsjeblieft de <a href="%1">releases pagina</a>op onze Github en download de versie voor jouw systeem. @@ -3559,131 +3572,131 @@ You may have to manually download the new version. Draw Probability Settings - + Raapkansinstellingen Criteria: - + Criteria: Card Name - + Kaartnaam Type - + Type Subtype - + Subtype Mana Value - + Manawaarde Exactness: - + Precisie: At least - + Minstens Exactly - + Precies Quantity (N): - + Kwantiteit (N): Cards drawn (M): - + Kaarten getrokken (M): cards - + kaarten DrawProbabilityWidget - - - Draw Probability - - - Probability of drawing - + Draw Probability + Raapkans - Card Name - - - - - Type - + Probability of drawing + Kans op rapen - Subtype - + Card Name + Kaartnaam - Mana Value - + Type + Type + + + + Subtype + Subtype - At least - + Mana Value + Manawaarde - - Exactly - + + At least + Minstens + Exactly + Precies + + + card(s) having drawn at least - + kaart(en) na het rapen van - + cards - + kaarten - + Category - + Kategorie - + Qty - + Hoeveel - + Odds (%) - + Kans (%) @@ -3691,12 +3704,12 @@ You may have to manually download the new version. In %1 decks - + In %1 decks %1% of %2 decks - + %1 van %2 decks @@ -3752,7 +3765,7 @@ You may have to manually download the new version. %1% Synergy - + %1% Synergie @@ -3760,7 +3773,7 @@ You may have to manually download the new version. Game Changers - + Game Changers @@ -3768,7 +3781,7 @@ You may have to manually download the new version. Budget - + Budget @@ -3776,12 +3789,12 @@ You may have to manually download the new version. Combos - + Combos Average Deck - + Gemiddeld Deck @@ -3789,7 +3802,7 @@ You may have to manually download the new version. Salt: - + Zoutigheid: @@ -3810,17 +3823,17 @@ You may have to manually download the new version. Are you sure you want to delete the filter '%1'? - + Weet je zeker dat je de filter '%1' wil verwijderen? Delete Failed - + Verwijderen Mislukt Failed to delete filter '%1'. - + Verwijderen van filter '%1' mislukt. @@ -3828,7 +3841,7 @@ You may have to manually download the new version. kicked by game host or moderator - + er uit geschopt door spelmaker of moderator @@ -3838,7 +3851,7 @@ You may have to manually download the new version. player disconnected from server - + speler verbrak verbinding van server @@ -3894,7 +3907,7 @@ You may have to manually download the new version. This game is only open to its creator's buddies. - Dit spel staat alleen open voor de vrienden van de maker van dit spel. + Dit spel is alleen toegankelijk voor de maatjes van de maker van dit spel. @@ -3919,12 +3932,12 @@ You may have to manually download the new version. Join Game as Judge - + Bij Spel Aansluiten als Judge Spectate Game as Judge - + Spel Toeschouwen als Judge @@ -3964,7 +3977,7 @@ You may have to manually download the new version. Join as judge - + Aansluiten als judge @@ -3974,7 +3987,7 @@ You may have to manually download the new version. Join as judge spectator - + Aansluiten als judge toeschouwer @@ -3992,32 +4005,32 @@ You may have to manually download the new version. All types - + Alle types Filter by game name... - + Filter op spelnaam... Filter by game type/format - + Filter op speltype/format Hide games not created by buddies - + Verberg spellen niet gemaakt door maatjes Hide full games - + Verberg volle spellen Hide started games - + Verberg begonnen spellen @@ -4052,7 +4065,7 @@ You may have to manually download the new version. buddies only - enkel vrienden + alleen maatjes @@ -4062,7 +4075,7 @@ You may have to manually download the new version. open decklists - + decklijsten gedeeld @@ -4129,529 +4142,549 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset alle bestandspaden - + All paths have been reset Alle bestandspaden zijn gereset - - - - - - - + + + + + + + Choose path Kies pad - + Personal settings Persoonlijke instellingen - + Language: Taal: - + Paths (editing disabled in portable mode) Locaties (bewerken uitgeschakeld in "portable mode") - + Paths Paden - + How to help with translations - + Hoe met vertalen te helpen - + Decks directory: Map voor decks: - + Filters directory: - + Filters map: - + Replays directory: Map voor replays: - + Pictures directory: Map voor afbeeldingen: - + Card database: Kaartdatabase: - + Custom database directory: Custom database map: - + Token database: Token-database - + Update channel Updatekanaal - - - Check for client updates on startup - - - Check for card database updates on startup - + Check for client updates on startup + Controleren op client-updates bij opstarten + Check for card database updates on startup + Controleren op kaartendatabasebewerkingen bij opstarten + + + Don't check - + Niet controleren - + Prompt for update - - - - - Always update in the background - + Vragen om bewerking - Check for card database updates every - + Always update in the background + Altijd in achtergrond bewerken + Check for card database updates every + Controleren op kaartendatabasebewerkingen iedere + + + days dagen - + Notify if a feature supported by the server is missing in my client Waarschuw als een functie die is ondersteund door de server mist in mijn client - + Automatically run Oracle when running a new version of Cockatrice Oracle automatisch uitvoeren bij het opstarten van een nieuwe versie van Cockatrice - + Show tips on startup Toon tips bij het opstarten - + Last update check on %1 (%2 days ago) - + Laatste controle op %1 (%2 dagen geleden) GraveyardMenu - + &Graveyard - + &Graveyard - + &View graveyard - - - - - &Move graveyard to... - + &Bekijk graveyard - &Top of library - + &Move graveyard to... + &Verplaats graveyard naar... - &Bottom of library - - - - - &All players - + &Top of library + &Bovenkant van library - &Hand - + &Bottom of library + &Onderkant van library + + + + &All players + &Alle spelers - &Exile - + &Hand + &Hand - + + &Exile + &Exile + + + Reveal random card to... - + Onthul willekeurige kaart aan... HandMenu - + &Hand - + &Hand - + &View hand - - - - - Sort hand by... - + &Bekijk hand - Name - + Sort hand by... + Sorteer hand op... - Type - + Name + Naam - Mana Value - + Type + Type - - Take &mulligan (Choose hand size) - + + Mana Value + Manawaarde - Take mulligan (Same hand size) - + Take &mulligan (Choose hand size) + Neem &mulligan (Kies handgrootte) - Take mulligan (Hand size - 1) - + Take mulligan (Same hand size) + Neem mulligan (Dezelfde handgrootte) - - &Move hand to... - + + Take mulligan (Hand size - 1) + Neem mulligan (Handgrootte - 1) - &Top of library - + &Move hand to... + &Verplaats hand naar.... - &Bottom of library - + &Top of library + &Bovenkant van library - &Graveyard - + &Bottom of library + &Onderkant van library - &Exile - + &Graveyard + &Graveyard - - &Reveal hand to... - + + &Exile + &Exile - - All players - + &Reveal hand to... + &Onthul hand aan.... - + + + All players + Alle spelers + + + Reveal r&andom card to... - + Onthul &willekeurige kaart aan.... HomeWidget - + Create New Deck - + Maak Nieuw Deck - + Browse Decks - + Decks Doorbladeren - + Browse Card Database - + Kaartendatabase Doorbladeren - + Browse EDHRec - + EDHRec Doorbladeren - + Browse Archidekt - + Archidekt Doorbladeren - + View Replays - + Replays Inzien - + Quit - + Sluiten - + Connecting... - + Verbinden... - + Connect - + Verbinden - + Play - + Spelen LibraryMenu - - - &Library - - - - - &View library - - - - - View &top cards of library... - - - - - View bottom cards of library... - - - - - Reveal &library to... - - - - - Lend library to... - - - - - Reveal &top cards to... - - - - - &Top of library... - - - - - &Bottom of library... - - - - - &Always reveal top card - - - - - &Always look at top card - - - - - &Open deck in deck editor - - - - - &Draw card - - - D&raw cards... - - - - - &Undo last draw - + &Library + &Library - Shuffle - + &View library + &Bekijk library + + + + View &top cards of library... + Bekijk &bovenste kaarten van library.... - &Play top card - + View bottom cards of library... + Bekijk onderste kaarten van library... - Play top card &face down - + Reveal &library to... + Onthul &library aan... - Put top card on &bottom - + Lend library to... + Leen library aan... - Move top card to grave&yard - + Reveal &top cards to... + &Onthul bovenste kaarten aan.... - Move top card to e&xile - + &Top of library... + &Bovenkant van library... - Move top cards to &graveyard... - + &Bottom of library... + &Onderkant van library... - Move top cards to &exile... - + &Always reveal top card + Toon &altijd bovenste kaart - Put top cards on stack &until... - + &Always look at top card + &Altijd bovenste kaart bekijken - Shuffle top cards... - + &Open deck in deck editor + &Open deck in deckbewerker - &Draw bottom card - + &Draw card + &Raap kaart - D&raw bottom cards... - + D&raw cards... + R&aap kaarten... - &Play bottom card - - - - - Play bottom card &face down - + &Undo last draw + &Ontdoe laatste raap - Move bottom card to grave&yard - - - - - Move bottom card to e&xile - + Shuffle + Schudden - Move bottom cards to &graveyard... - + &Play top card + &Speel bovenste kaart - Move bottom cards to &exile... - + Play top card &face down + Speel bovenste kaart &verdekt - Put bottom card on &top - + Put top card on &bottom + Leg bovenste kaart aan de &onderkant + Move top card to grave&yard + Verplaats bovenste kaart naar grave&yard + + + + Move top card to e&xile + Verplaats bovenste kaart naar exile + + + + Move top cards to &graveyard... + Verplaats bovenste kaarten naar &graveyard.... + + + + Move top cards to graveyard face down... + Verplaats bovenste kaarten naar &graveyard verdekt.... + + + + Move top cards to &exile... + Verplaats bovenste kaarten naar &exile.... + + + + Move top cards to exile face down... + Verplaats bovenste kaarten naar exile verdekt.... + + + + Put top cards on stack &until... + Plaats bovenste kaart op stack &totdat... + + + + Shuffle top cards... + Schud bovenste kaarten... + + + + &Draw bottom card + &Raap onderste kaart + + + + D&raw bottom cards... + R&aap onderste kaarten... + + + + &Play bottom card + &Speel onderste kaart + + + + Play bottom card &face down + Speel onderste kaart &verdekt + + + + Move bottom card to grave&yard + Verplaats onderste kaart naar grave&yard + + + + Move bottom card to e&xile + Verplaats onderste kaart naar e&xile + + + + Move bottom cards to &graveyard... + Verplaats onderste kaarten naar &graveyard.... + + + + Move bottom cards to graveyard face down... + Verplaats onderste kaarten naar graveyard verdekt.... + + + + Move bottom cards to &exile... + Verplaats onderste kaarten naar &exile... + + + + Move bottom cards to exile face down... + Verplaats onderste kaarten naar exile verdekt... + + + + Put bottom card on &top + Leg onderste kaart &bovenop + + + Shuffle bottom cards... - + Schud onderste kaarten... - - + + &All players - + &Alle spelers - + Reveal top cards of library - + Onthul bovenste kaarten van library - + Number of cards: (max. %1) - + Aantal kaarten: (max. %1) @@ -4747,18 +4780,8 @@ Will now login. Logt nu in. - - Number of players - Aantal spelers - - - - Please enter the number of players. - Voer het aantal spelers in. - - - - + + Player %1 Speler %1 @@ -4861,8 +4884,8 @@ Logt nu in. - - + + Error Fout @@ -5066,7 +5089,7 @@ Dit betekent meestal dat uw clientversie verouderd is en dat de server een antwo The connection to the server has been lost. - + De verbinding met de server is verloren gegaan. @@ -5137,7 +5160,7 @@ Lokale versie is %1, externe versie is %2. Start &local game... - Start &lokaal spel.... + Start &plaatselijk spel.... @@ -5212,12 +5235,12 @@ Lokale versie is %1, externe versie is %2. Reload card database - + Kaartendatabase opniew laden Tabs - + Tabs @@ -5247,12 +5270,12 @@ Lokale versie is %1, externe versie is %2. Check for Card Updates (Automatic) - + Controleren op kaartbewerkingen (Automatisch) Show Status Bar - + Statusbalk Weergeven @@ -5262,7 +5285,7 @@ Lokale versie is %1, externe versie is %2. Open Settings Folder - + Instellingsmap Openen @@ -5270,65 +5293,65 @@ Lokale versie is %1, externe versie is %2. Tonen/Verbergen - + New Version Niewe Versie - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gefeliciteerd met de update naar Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Cockatrice installed Cockatrice geinstalleerd - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gefeliciteerd met het installeren van Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Card database Kaartdatabase - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice kan de kaartendatabase niet laden. - - + + Yes Ja - - + + No Nee - + Open settings Open instellingen - + New sets found Nieuwe sets gevonden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5339,17 +5362,17 @@ Set codes: %1 Wilt u deze inschakelen? - + View sets Bekijk sets - + Welcome Welkom - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5358,64 +5381,65 @@ Alle sets in de kaartendatabase zijn ingeschakeld. Lees meer over het wijzigen van de setvolgorde of het uitschakelen van specifieke sets en de daaruit voortvloeiende effecten in het "Beheer sets" dialoogvenster. - - + + Information Informatie - + A card database update is already running. Een update van de kaartendatabase is al aan de gang. - + Unable to run the card database updater: Kan het updateprogramma van de kaartendatabase niet uitvoeren: - + Card database update running. - + Kaartendatabasebewerking in uitvoering. - + Failed to start. The file might be missing, or permissions might be incorrect. - + Opstarten mislukt. Het bestand mist misschien, of toestemmingen zijn misschien incorrect. - + The process crashed some time after starting successfully. - + Het proces crashte nadat deze geslaagd is opgestart. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + Tijd verlopen. Het proces nam te lang met antwoorden. De laatste waitfor...() functie verliep. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + Een fout heeft zich voorgedaan tijdens een poging om te schrijven naar het proces. Bijvoorbeeld, het proces is niet in uitvoering misschien, of het heeft misschien het invoerkanaal gesloten. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Een fout heeft zich voorgedaan tijdens een poging om te lezen van het proces. Bijvoorbeeld, het proces is niet in uitvoering misschien. - + Unknown error occurred. - + Een onbekende fout heeft zich voorgedaan. - + The card database updater exited with an error: %1 - + De kaartendatabasebewerker sloot af met foutmelding. +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5426,55 +5450,55 @@ Dit is waarschijnlijk geen probleem, maar dit bericht kan betekenen dat er een n Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - - - - - + + + + + Load sets/cards Laad sets/kaarten - + Selected file cannot be found. Geselecteerd bestand kan niet worden gevonden. - + You can only import XML databases at this time. U kunt op dit moment alleen XML-databases importeren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. De nieuwe sets/kaarten zijn succesvol toegevoegd. Cockatrice zal nu de kaartendatabase herladen. - + Sets/cards failed to import. Sets/kaarten zijn niet geïmporteerd. - - - + + + Reset Password Reset Wachtwoord - + Your password has been reset successfully, you can now log in using the new credentials. Uw wachtwoord is succesvol gereset, u kunt nu inloggen met de nieuwe gegevens. - + Failed to reset user account password, please contact the server operator to reset your password. Het opnieuw instellen van het wachtwoord voor uw gebruikersaccount is mislukt, neem contact op met de serverbeheerder om uw wachtwoord opnieuw in te stellen. - + Activation request received, please check your email for an activation token. Activeringsaanvraag ontvangen, kijk in uw e-mail voor een activeringscode. @@ -5484,50 +5508,50 @@ Cockatrice zal nu de kaartendatabase herladen. Mana Base Configuration - + Manabasis Configuratie Display type: - + Soort weergave: pie - + taart bar - + balk combinedBar - + combinatieBalk Filter Colors (optional): - + Filter Kleuren (optioneel): OK - + OK Cancel - + Annuleren ManaBaseWidget - + Mana Base - + Manabasis @@ -5535,47 +5559,47 @@ Cockatrice zal nu de kaartendatabase herladen. Group By: - + Groepeer Op: type - + type color - + kleur subtype - + subtype power - + kracht toughness - + hardheid Filters (optional): - + Filters (optioneel): Show main bar row - + Hoofdbalk rij weergeven Show per-category rows - + Rij per kategorie weergeven @@ -5583,7 +5607,7 @@ Cockatrice zal nu de kaartendatabase herladen. Mana Curve - + Manacurve @@ -5591,27 +5615,27 @@ Cockatrice zal nu de kaartendatabase herladen. Display type: - + Soort weergave: pie - + taart bar - + balk combinedBar - + combinatieBalk Filter Colors (optional): - + Filter Kleuren (optioneel): @@ -5619,7 +5643,7 @@ Cockatrice zal nu de kaartendatabase herladen. Mana Devotion - + Manatoewijding @@ -5627,27 +5651,27 @@ Cockatrice zal nu de kaartendatabase herladen. Top display type: - + Bovenste soort weergave: pie - + taart bar - + balk Colors: - + Kleuren: Show per-color rows - + Rij per kleur weergeven @@ -5655,12 +5679,12 @@ Cockatrice zal nu de kaartendatabase herladen. %1 pips (%2 cards) - + %1 stipjes (%2 kaarten) %1 mana (%2 cards) - + %1 mana (%2 kaarten) @@ -5668,601 +5692,621 @@ Cockatrice zal nu de kaartendatabase herladen. Mana Production + Devotion - + Manaproductie/toewijding Mana Distribution Settings - + Manadistributieinstellingen MessageLogWidget - + from play vanuit spel - + from their graveyard vanuit hun graveyard. - + from exile vanuit exile - + from their hand vanuit hun hand. - + the top card of %1's library de bovenste kaart %1's library - + the top card of their library de bovenste kaart van hun library - + from the top of %1's library van de top van %1's library - + from the top of their library van de bovenkant van hun library - + the bottom card of %1's library de onderste kaart van %1's library - + the bottom card of their library de onderste kaart van hun library - + from the bottom of %1's library van de onderkant van %1's library - + from the bottom of their library van de onderkant van hun library - + from %1's library van %1's library - + from their library van hun library - + from sideboard van sideboard - + from the stack van de stack - + from custom zone '%1' - + vanaf aangepaste zone '%1' - + %1 is now keeping the top card %2 revealed. %1 houdt nu de bovenste kaart %2 onthuld. - + %1 is not revealing the top card %2 any longer. %1 geeft de bovenste kaart %2 niet meer weer. - + %1 can now look at top card %2 at any time. %1 kan nu altijd de bovenste kaart %2 bekijken. - + %1 no longer can look at top card %2 at any time. %1 kan nu niet meer de bovenste kaart %2 bekijken. - + %1 attaches %2 to %3's %4. %1 bevestigt %2 aan %3s %4. - + %1 has conceded the game. %1 heeft het spel opgegeven. - + %1 has unconceded the game. %1 is terug in het spel. - + %1 has restored connection to the game. %1 heeft de verbinding met het spel hersteld. - + %1 has lost connection to the game. %1 heeft de verbinding met het spel verloren. - + %1 points from their %2 to themselves. %1 wijst van hun %2 naar zichzelf. - + %1 points from their %2 to %3. %1 wijst van hun %2 naar %3. - + %1 points from %2's %3 to themselves. %1 wijst van %2s %3 naar zichzelf. - + %1 points from %2's %3 to %4. %1 wijst van %2s %3 naar %4. - + %1 points from their %2 to their %3. %1 wijst van hun %2 naar hun %3. - + %1 points from their %2 to %3's %4. %1 wijst van hun %2 naar %3s %4. - + %1 points from %2's %3 to their own %4. %1 wijst van %2s %3 naar hun eigen %4. - + %1 points from %2's %3 to %4's %5. %1 wijst van %2s %3 naar %4s %5. - + %1 creates a face down token. - + %1 creëert een token verdekt. - + %1 creates token: %2%3. %1 creëert een token: %2%3. - + %1 has loaded a deck (%2). %1 heeft een deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 heeft een deck met %2 sideboard kaarten geladen (%3). - + %1 destroys %2. %1 vernietigt %2. - + a card een kaart - + %1 gives %2 control over %3. %1 geeft %2 controle over %3. - + %1 puts %2 into play%3 face down. %1 zet %2 gesloten in het spel%3. - + %1 puts %2 into play%3. %1 zet %2 in het spel%3. - + + %1 puts %2%3 into their graveyard face down. + %1 legt %2%3 in hun graveyard verdekt. + + + %1 puts %2%3 into their graveyard. %1 legt %2%3 in hun graveyard. - - %1 exiles %2%3. - %1 exiled %2%3. + + %1 exiles %2%3 face down. + %1 legt %2%3 in exile verdekt. - + + %1 exiles %2%3. + %1 legt %2%3 in exile. + + + %1 moves %2%3 to their hand. %1 verplaatst %2%3 naar hun hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in hun library. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 aan de onderkant van hun library. - + %1 puts %2%3 on top of their library. %1 legt %2%3 op de bovenkant van hun library. - + %1 puts %2%3 into their library %4 cards from the top. %1 plaatst %2%3 in hun library %4 kaarten vanaf de bovenkant. - + %1 moves %2%3 to sideboard. %1 verplaatst %2%3 naar het sideboard. - + + %1 plays %2%3 face down. + %1 speelt %2%3 verdekt. + + + %1 plays %2%3. %1 speelt %2%3. - - %1 moves %2%3 to custom zone '%4'. - + + %1 moves %2%3 to custom zone '%4' face down. + %1 verplaatst %2%3 naar aangepaste zone '%4' verdekt. - + + %1 moves %2%3 to custom zone '%4'. + %1 verplaatst %2%3 naar aangepaste zone '%4'. + + + %1 tries to draw from an empty library %1 probeert een kaart te nemen van een lege library - + %1 draws %2 card(s). %1 trekt een kaart%1 trekt %2 kaarten - + %1 is looking at %2. %1 is aan het kijken naar %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + %1 kijkt naar de %4 %3 kaart %2.%1 kijkt naar de %4 %3 kaarten %2. - + bottom - + onderkant - + top - + bovenkant - + %1 turns %2 face-down. %1 draait %2 om met de voorkant naar beneden. - + %1 turns %2 face-up. %1 draait %2 om met de voorkant naar boven. - + The game has been closed. Het spel is afgesloten. - + The game has started. Het spel is begonnen. - + You are flooding the game. Please wait a couple of seconds. - + Je bent het spel aan het overstelpen. Wacht een paar seconden. - + %1 has joined the game. %1 heeft zich bij het spel aangesloten. - + %1 is now watching the game. %1 kijkt nu naar het spel. - + You have been kicked out of the game. Je bent uit het spel geschopt. - + %1 has left the game (%2). %1 heeft het spel verlaten (%2). - + %1 is not watching the game any more (%2). %1 kijkt niet meer naar het spel (%2). - + %1 is not ready to start the game any more. %1 is niet meer klaar om het spel te starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaart.%1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaarten. - + %1 shuffles their deck and draws a new hand. %1 schudt zijn of haar deck en trekt een nieuwe hand. - + You are watching a replay of game #%1. Je kijkt naar een replay van spel #%1. - + %1 is ready to start the game. %1 is klaar om het spel te starten. - + cards an unknown amount of cards kaarten - + %1 card(s) a card for singular, %1 cards for plural één kaart%1 kaarten - + %1 lends %2 to %3. %1 leent %2 aan %3. - + %1 reveals %2 to %3. %1 onthult %2 naar %3. - + %1 reveals %2. %1 onthult %2. - + %1 randomly reveals %2%3 to %4. %1 onthult %2%3 aan %4 gebaseerd op willekeur. - + %1 randomly reveals %2%3. %1 onthult %2%3 gebaseerd op willekeur. - + %1 peeks at face down card #%2. %1 bekijkt verdekte kaart #%2. - + %1 peeks at face down card #%2: %3. %1 bekijkt verdekte kaart #%2: %3. - + %1 reveals %2%3 to %4. %1 onthult %2%3 aan %4. - + %1 reveals %2%3. %1 onthult %2%3. - + %1 reversed turn order, now it's %2. %1 keert de beurtvolgorde om, deze is nu %2. - + reversed omgekeerd - + normal normaal - + Heads Kop - + Tails Munt - + %1 flipped a coin. It landed as %2. %1 gooit een muntje om. Het landde %2. - + %1 rolls a %2 with a %3-sided die. %1 rolt een %2 met een %3-zijdige dobbelsteen. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 werpt %2 munten. Er zijn %3 koppen en %4 munten. - + %1 rolls a %2-sided dice %3 times: %4. %1 werpt een %2-zijdige dobbelsteen %3 keer: %4. - + %1's turn. %1s beurt. - + %1 sets annotation of %2 to %3. %1 verandert de annotatie van %2 naar %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 plaatst %2 "%3" counter op %4 (nu %5).%1 plaatst %2 "%3" counters op %4 (nu %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 verwijderd %2 "%3" counter van %4 (nu %5).%1 verwijderd %2 "%3" counters van %4 (nu %5). - + %1 sets counter %2 to %3 (%4%5). %1 verandert teller %2 naar %3 (%4%5). - + %1 sets %2 to not untap normally. %1 zet %2 op niet tegelijk ontappen. - + %1 sets %2 to untap normally. %1 zet %2 op tegelijk ontappen. - + %1 removes the PT of %2. %1 verwijdert de stats van %2. - + %1 changes the PT of %2 from nothing to %4. %1 verandert de stats van %2 van niks naar %4. - + %1 changes the PT of %2 from %3 to %4. %1 verandert de stats van %2 van %3 naar %4. - + %1 has locked their sideboard. %1 heeft hun sideboard vergrendeld. - + %1 has unlocked their sideboard. %1 heeft hun sideboard ontgrendeld. - + %1 taps their permanents. %1 tapt alles. - + %1 untaps their permanents. %1 ontapt alles. - + %1 taps %2. %1 tapt %2. - + %1 untaps %2. %1 ontapt %2. - + %1 shuffles %2. %1 schudt %2. - + %1 shuffles the bottom %3 cards of %2. %1 schudt de onderste %3 kaarten van %2. - + %1 shuffles the top %3 cards of %2. %1 schudt de bovenste %3 kaarten van %2. - + %1 shuffles cards %3 - %4 of %2. %1 schudt kaarten %3 - %4 van %2. - + %1 unattaches %2. %1 maakt %2 los. - + %1 undoes their last draw. %1 legt hun laatst getrekte kaart terug. - + %1 undoes their last draw (%2). %1 legt hun laats getrekte kaart terug (%2). @@ -6270,110 +6314,110 @@ Cockatrice zal nu de kaartendatabase herladen. MessagesSettingsPage - + Word1 Word2 Word3 Woord1 Woord2 Woord3 - + Add New Message Bericht toevoegen - + Edit Message Bericht bewerken - + Remove Message Bericht Verwijderen - + Add message Bericht toevoegen - - + + Message: Bericht: - + Edit message Bericht bewerken - + Chat settings Chat instellingen - + Custom alert words Aanpasbare alerteringswoorden - + Enable chat mentions Schakel chat vermeldingen in - + Enable mention completer Vermeldings aanvuller inschakelen - + In-game message macros In-game bericht macro's - + How to use in-game message macros Uitleg gebruik van in-game bericht macro's - + Ignore chat room messages sent by unregistered users Chatroomberichten van niet-geregistreerde gebruikers negeren - + Ignore private messages sent by unregistered users Privéberichten van niet-geregistreerde gebruikers negeren - - + + Invert text color Text kleur inverteren - + Enable desktop notifications for private messages Desktopmeldingen voor privéberichten inschakelen - + Enable desktop notification for mentions Desktopmelding voor vermeldingen inschakelen - + Enable room message history on join Kamerboodschapsgeschiedenis op aansluiting inschakelen - - + + (Color is hexadecimal) (Kleur is hexadecimaal) - + Separate words with a space, alphanumeric characters only Woorden scheiden met een spatie, alleen alfanumerieke tekens @@ -6383,37 +6427,42 @@ Cockatrice zal nu de kaartendatabase herladen. Move to - + Verplaats naar - + &Top of library in random order - + %bovenkant library in willekeurige volgorde - + X cards from the top of library... - + X kaarten van bovenkant library... - + &Bottom of library in random order - + &Onderkant library in willekeurige volgorde - + + T&able + &Tafel + + + &Hand - + &Hand - + &Graveyard - + &Graveyard - + &Exile - + &Exile @@ -6461,7 +6510,7 @@ Cockatrice zal nu de kaartendatabase herladen. Layout - Lay-out + Indeling @@ -6474,17 +6523,17 @@ Cockatrice zal nu de kaartendatabase herladen. Cards to overlap: - + Kaarten overlap: Overlap percentage: - + Overlap percentage: Overlap direction: - + Overlap richting: @@ -6553,57 +6602,57 @@ Cockatrice zal nu de kaartendatabase herladen. PhasesToolbar - + Untap step Untap fase - + Upkeep step Upkeep fase - + Draw step Draw fase - + First main phase Eerste hoofdfase - + Beginning of combat step Begin vechtfase - + Declare attackers step Aanvallers aangeven fase - + Declare blockers step Verdedigers aangeven fase - + Combat damage step Gevechtsschade fase - + End of combat step Einde vechtfase - + Second main phase Tweede hoofdfase - + End of turn step Einde van de beurt fase @@ -6620,185 +6669,210 @@ Cockatrice zal nu de kaartendatabase herladen. PlayerActions - + View top cards of library - + Bekijk bovenste kaarten van library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + Aantal kaarten: (max. %1) - + View bottom cards of library - + Bekijk onderste kaarten library - + Shuffle top cards of library - + Schud bovenste kaarten library - + Shuffle bottom cards of library - + Schud onderste kaarten library - + Draw hand - + Raap hand - + 0 and lower are in comparison to current hand size - + 0 en minder zijn in verhouding tot huidige handgrootte - + Draw cards - + Raap kaarten + + + + + + + grave + graveyard - Move top cards to grave - + + + + exile + exile - - Move top cards to exile - + + Move top cards to %1 + Verplaats bovenste kaarten naar %1 - - Move bottom cards to grave - + + Move bottom cards to %1 + Verplaats onderste kaarten naar %1 - - Move bottom cards to exile - - - - + Draw bottom cards - + Raap onderste kaarten - - + + C&reate another %1 token - + &Maak nog een %1 token - + Create tokens - + Token maken - - + + Number: - + Aantal: - + Place card X cards from top of library - + Plaats kaart X kaarten vanaf bovenkant van library - + Which position should this card be placed: - + Hoeveelste van boven moet de kaart geplaatst worden: - + (max. %1) - + (max. %1) - + Change power/toughness - + Verander kracht/hardheid - + Change stats to: - + Verander stats naar: - + Set annotation - + Aantekening instellen - + Please enter the new annotation: - + Voer de nieuwe aantekening in: - + Set counters - + Counters instellen PlayerMenu - + Player "%1" - + Speler "%1" - + &Counters - + &Counters + + + + PrintingDisabledInfoWidget + + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + De Drukkiezer is uitgeschakeld omdat je op het moment de instelling om alle gekozen drukken te overschrijven met persoonlijke setvoorkeuren is ingeschakeld. + +Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart zal zien in plaats van een druk te kiezen, en je zal drukken die anderen gekozen hebben niet zien. + + - - S&ay - + + Enable printings again + Drukkiezer opnieuw inschakelen PrintingSelector - + Display Navigation Buttons - + Navigatieknoppen weergeven + + + + Printing Selector + Drukkiezer PrintingSelectorCardOverlayWidget - + Preference - + Voorkeur - + Pin Printing - + Druk Vastpinnen - + Unpin Printing - + Druk Lospinnen - + Show Related cards - + Gerelateerde kaarten weergeven @@ -6806,7 +6880,7 @@ Cockatrice zal nu de kaartendatabase herladen. Search by set name or set code - + Zoeken op setnaam of setcode @@ -6814,17 +6888,17 @@ Cockatrice zal nu de kaartendatabase herladen. Previous Card in Deck - + Vorige Kaart in Deck Bulk Selection - + Massaselectie Next Card in Deck - + Volgende Kaart in Deck @@ -6832,28 +6906,36 @@ Cockatrice zal nu de kaartendatabase herladen. Alphabetical - + Alphabetisch Preference - + Voorkeur Release Date - + Datum van Uitgave - - + + Descending - + Omlaag - + Ascending - + Omhoog + + + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + Selekteer een kaar om diens drukken te weergeven @@ -6861,57 +6943,57 @@ Cockatrice zal nu de kaartendatabase herladen. Power / toughness - + Kracht / hardheid &Increase power - + Ver&hoog kracht &Decrease power - + Ver&laag kracht I&ncrease toughness - + Verh&oog hardheid D&ecrease toughness - + Verl&aag hardheid In&crease power and toughness - + Verhoo&g kracht en hardheid Dec&rease power and toughness - + Verlaa&g kracht en hardheid Increase power and decrease toughness - + Verhoog kracht en verlaag hardheid Decrease power and increase toughness - + Verlaag kracht en verhoog hardheid Set &power and toughness... - + Kracht en hardheid &instellen... Reset p&ower and toughness - + &Herstel kracht en hardheid @@ -6987,12 +7069,51 @@ Cockatrice zal nu de kaartendatabase herladen. Overwrite Existing File? - + Bestaand Bestand Overschrijven? A .cod version of this deck already exists. Overwrite it? - + Een .cod versie van dit deck bestaat al. Deze overschrijven? + + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + Het inschakelen van deze functie zal het gebruik van de drukkiezer uitschakelen. + +Je zal niet meer kunnen kiezen welke druk in gebruk is voor verschillende decks, of kunnen zien welke drukken anderen hebben gekozen voor hun decks. + +Je zal de Setbeheerder moeten gebruiken, te vinden bij Kaartendatabase -> Beheer Sets. + +Weet je zeker dat je deze functie wil inschakelen? + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + Het uitschakelen van deze functie zal de Drukkiezer inschakelen. + +Je kan nu drukken kiezen voor verschillende decks in de Deckbewerker en instellen welke druk standaard wordt toegevoegd door deze vast te pinnen in de Drukkiezer. + +Je ook de Setbeheerder gebruiken om de sorteervolgorde voor de drukken in de Drukkiezer aan te passen (andere soorteervolgorden zoals alphabetisch of datum van uitgave zijn beschikbaar). + +Weet je zeker dat je deze functie wil uitschakelen? + + + + Confirm Change + Bevestig Wijziging @@ -7141,40 +7262,40 @@ Cockatrice zal nu de kaartendatabase herladen. RfgMenu - - - &Exile - - + &Exile + &Exile + + + &View exile - - - - - &Move exile to... - - - - - &Top of library - + &Bekijk exile - &Bottom of library - + &Move exile to... + &Verplaats exile naar... - &Hand - + &Top of library + &Bovenkant library + &Bottom of library + &Onderkant library + + + + &Hand + &Hand + + + &Graveyard - + &Graveyard @@ -7215,6 +7336,14 @@ Cockatrice zal nu de kaartendatabase herladen. Spellen + + SayMenu + + + S&ay + &Zeg + + SequenceEdit @@ -7235,7 +7364,7 @@ Cockatrice zal nu de kaartendatabase herladen. Shortcut already in use by: - + Sneltoets reeds in gebruik door: @@ -7279,55 +7408,55 @@ Cockatrice zal nu de kaartendatabase herladen. ShortcutSettingsPage - - + + Restore all default shortcuts Alle standaard snelkoppelingen herstellen - + Do you really want to restore all default shortcuts? Wilt u echt alle standaard snelkoppelingen herstellen? - + Clear all default shortcuts Alle standaard snelkoppelingen wissen - + Do you really want to clear all shortcuts? Wil je echt alle snelkoppelingen vrijmaken? - + Section: Sectie: - + Action: Functie: - + Shortcut: Sneltoets: - + How to set custom shortcuts Hoe aangepaste snelkoppelingen in te stellen - + Clear all shortcuts Alle sneltoetsen wissen - + Search by shortcut name - + Zoek op sneltoetsnaam @@ -7335,12 +7464,12 @@ Cockatrice zal nu de kaartendatabase herladen. Action - + Functie Shortcut - + Sneltoets @@ -7383,38 +7512,38 @@ Controleer uw snelkoppelingsinstellingen! &Sideboard - + &Sideboard &View sideboard - + &Bekijk sideboard SoundSettingsPage - + Enable &sounds &Geluiden inschakelen - + Current sounds theme: Huidige geluiden thema: - + Test system sound engine Test geluid engine van systeem - + Sound settings Geluidsinstellingen - + Master volume Hoofdvolume @@ -7473,7 +7602,7 @@ Controleer uw snelkoppelingsinstellingen! Default - + Standaard @@ -7501,17 +7630,17 @@ Controleer uw snelkoppelingsinstellingen! Add to Buddy List - + Aan Maatjeslijst toevoegen Add to Ignore List - + Voeg toe aan Negeerlijst Account - + Account @@ -7544,27 +7673,27 @@ Controleer uw snelkoppelingsinstellingen! Server moderator functions - + Servermoderatiefuncties Replay ID - + Replay ID Grant Replay Access - + Geef Toegang Replay Username to Activate - + Te Activeren Gebruikersnaam Force Activate User - + Forceer Activeren Gebruiker @@ -7580,12 +7709,12 @@ Controleer uw snelkoppelingsinstellingen! Success - + Succesvol Replay access granted - + Toegang replay toegekend @@ -7594,156 +7723,212 @@ Controleer uw snelkoppelingsinstellingen! Error - + Foutmelding Unable to grant replay access. Replay ID invalid - + Niet mogelijk om toegang tot replay te geven. Replay ID ongeldig Unable to grant replay access. Internal error - + Niet mogelijk om toegang tot replay te geven. Interne fout User successfully activated - + Gebruiker succesvol geactiveerd Unable to activate user. Username invalid - + Niet mogelijk om gebruiker te activeren. Gebruikersnaam ongeldig Unable to activate user. User already active - + Niet mogelijk om gebruiker te activeren. Gebruiker reeds geactiveerd Unable to activate user. Internal error - + Niet mogelijk om gebruiker te activeren. Interne fout TabArchidekt - - + + + Desc. - + Afl. - - Asc. - + + + AND + EN - - - Any Bracket - + + + Require ALL selected colors + Vereis ALLE gekozen kleuren - - Deck name contains... - + + + Deck name... + Decknaam... - - Owner name contains... - + + + Owner... + Eigenaar... + + + + + Packages + Pakketten + + + + + Advanced Filters + Geavanceerde Filters - Disabled - + Bracket: + Bracket: - + + + Any + Geen Voorkeur + + + + + Contains card... + Bevat kaart... + + + + + Commander... + Commander... + + + + + Tag... + Tag... + + + + + Deck Size + Deckgrootte + + + + Cards: + Kaarten: + + + + + Asc. + Opl. + + + + Sort by: + Sorteer op: + + + + Filter by: + Filter op: + + + + Display Settings + Weergavensinstellingen + + + + Search - + Zoeken - + + Formats - + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: - + Archidekt: TabDeckEditor - + Card Info Kaart info - + Deck Deck - + Filters Filters - + &View &Bekijk - + Card Database - + Kaartendatabase - + Printing - + Druk - - - - - + Visible Zichtbaar - - - - - + Floating Zwevend - + Reset layout - Layout resetten + Indeling resetten - + Deck: %1 Dek: %1 @@ -7751,63 +7936,57 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditorVisual - + Visual Deck: %1 - + Visueel Deck: %1 - + &Visual Deck Editor - + &Visuele Deckbewerker - - + + Card Info - + Kaarteninfo - - + + Deck - + Deck - - + + Filters - + Filters - + &View - + &Bekijk - + Printing - + Druk - - - - + Visible - + Zichtbaar - - - - + Floating - + Zwevend - + Reset layout - + Herstel indeling @@ -7815,22 +7994,22 @@ Controleer uw snelkoppelingsinstellingen! Visual Deck View - + Visuele Deckweergave Visual Database Display - + Visuele Databaseweergave Deck Analytics - + Deckanalyses Sample Hand - + Proefhand @@ -7838,7 +8017,7 @@ Controleer uw snelkoppelingsinstellingen! Local file system - Lokaal bestandssysteem + Plaatselijk bestandssysteem @@ -7849,12 +8028,12 @@ Controleer uw snelkoppelingsinstellingen! Open in deck editor - Open dek in editor + Open deck in bewerker Rename deck or folder - + Hernoem deck of map @@ -7870,9 +8049,9 @@ Controleer uw snelkoppelingsinstellingen! - + New folder - Nieuwe folder + Nieuwe map @@ -7883,22 +8062,22 @@ Controleer uw snelkoppelingsinstellingen! Open decks folder - + Deckmap openen Rename local folder - + Hernoem plaatselijke map Rename local file - + Hernoem plaatselijk bestand New name: - + Nieuwe naam: @@ -7911,7 +8090,7 @@ Controleer uw snelkoppelingsinstellingen! Rename failed - + Hernoemen mislukt @@ -7943,33 +8122,33 @@ Please enter a name: Delete local file - Verwijder lokaal bestand + Verwijder plaatselijk bestand Are you sure you want to delete the selected files? - + Weet je zeker dat je de opgeslagen bestanden wil verwijderen? - + Delete remote decks - + Verwijder deck van server - + Are you sure you want to delete the selected decks? - + Weet je zeker dat je de geselekteerde decks wil verwijderen? - + Name of new folder: - Naam van de folder: + Naam van de map: Deck Storage - + Deckopslag @@ -7977,17 +8156,17 @@ Please enter a name: Visual Deck Storage - + Visuele Deckopslag - + Error - + Foutmelding - + Could not open deck at %1 - + Kon deck op %1 niet openen @@ -7995,7 +8174,7 @@ Please enter a name: EDHREC: - + EDHREC: @@ -8003,228 +8182,222 @@ Please enter a name: &Cards - + &Kaarten Top Commanders - + Top Commanders Tags - + Tags Search for a card ... - + Zoeken op kaart... Search - + Zoeken EDHRec: - + EDHRec: TabGame - - - + + + Replay Replay - - + + Game Spel - - + + Player List Spelerslijst - - + + Card Info Kaartinfo - - + + Messages Berichten - - + + Replay Timeline Replay Tijdslijn - + &Phases Fases - + &Game Spel - + Next &phase Volgende fase - + Next phase with &action Volgende fase met &handeling - + Next &turn Volgende beurt - + Reverse turn order Keer beurtvolgorde om - + &Remove all local arrows - Verwijder alle lokale pijlen + Verwijder alle plaatselijke pijlen - + Rotate View Cl&ockwise Draai de weergave met de klok &mee - + Rotate View Co&unterclockwise Draai de weergave &tegen de klok in - + Game &information Spelgegevens - + Un&concede - + Opgeven &terugdraaien - - - + + + &Concede &Geef op - + &Leave game Verlaat spel - + C&lose replay Sluit opgeslagen spel - + &Focus Chat &Focus Chat - + &Say: Zeg: - + Selected cards - + Geselecteerde kaarten - + &View &Bekijk - - - - + Visible Zichtbaar - - - - + Floating Zwevend - + Reset layout - Reset lay-out + Reset indeling - + Concede Opgeven - + Are you sure you want to concede this game? Ben je zeker dat je wilt opgeven voor dit spel? - + Unconcede Opgeven ongedaan maken - + You have already conceded. Do you want to return to this game? Je hebt al opgegeven. Wil je nar dit spel terugkeren? - + Leave game Verlaat spel - + Are you sure you want to leave this game? Ben je zeker dat je dit spel wilt verlaten? - + A player has joined game #%1 Een speler sluitte aan bij spel #%1 - + %1 has joined the game %1 sluitte bij het spel aan - + You have been kicked out of the game. Je bent uit het spel gegooid. @@ -8234,7 +8407,7 @@ Please enter a name: Home - + Thuis @@ -8439,7 +8612,7 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Local file system - Lokaal bestandssysteem + Plaatselijk bestandssysteem @@ -8455,13 +8628,13 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Rename - + Hernoemen New folder - + Nieuwe map @@ -8472,7 +8645,7 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Open replays folder - + Open replays map @@ -8487,127 +8660,128 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Get replay share code - + Ontvang replay-deelcode Look up replay by share code - + Replay opzoeken met replay-deelcode Rename local folder - + Hernoem plaatselijke map Rename local file - + Hernoem plaatselijk bestand New name: - + Nieuwe naam: Error - + Foutmelding Rename failed - + Hernoemen mislukt Name of new folder: - + Naam van nieuwe map: Delete local file - Verwijder lokaal opgeslagen spel + Verwijder plaatselijk bestand Are you sure you want to delete the selected files? - + Weet je zeker dat je de geselecteerde bestanden wil verwijderen? Are you sure you want to delete the selected replays? - + Weet je zeker dat je de geselecteerde replays wil verwijderen? Failed to get code - + Ontvangen code mislukt Either this server does not support replay sharing, or does not permit replay sharing for you. - + Deze server ondersteund het delen van replays niet of deze staat het niet toe aan jou. Failed - + Mislukt Could not get replay code - + Ontvangen replay-code niet gelukt Replay Share Code - + Replay-deelcode Others can use this code to add the replay to their list of remote replays: %1 - + Anderen kunnen deze code gebruiken om de replay aan hun lijst van serverreplays toe te voegen: +%1 Copy to clipboard - + Kopieer naar klembord Replay share code - + Replay-deelcode Replay code found - + Replay-code gevonden Replay was added, or you already had access to it. - + Replay toegevoegd, of je had misschien al toegang tot deze. Replay code not found - + Replay-code niet gevonden Failed to submit code - + Versturen code mislukt Unexpected error - + Onverwachte foutmelding @@ -8617,7 +8791,7 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Game Replays - + Spel-replays @@ -8660,7 +8834,7 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Click to view - Click om weer te geven + Klik om weer te geven @@ -8709,67 +8883,67 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. Deck Editor - + Deckbewerker Visual Deck Editor - + Visuele Deckbewerker EDHRec - + EDHRec Archidekt - + Archidekt Home - + Thuis &Visual Deck Storage - + &Visuele Deckopslag Visual Database Display - + Visuele Databaseweergave Server - + Server Account - + Account Deck Storage - + Deckopslag Game Replays - + Spel-replays Administration - + Beheer Logs - + Logs @@ -8852,12 +9026,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Database Display - + Databaseweergave Visual Database Display - + Visuele Databaseweergave @@ -8962,12 +9136,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Add to &buddy list - Voeg toe aan vriendenlijst + Voeg toe aan maatjeslijst Remove from &buddy list - Verwijder uit vriendenlijst + Verwijder uit maatjeslijst @@ -9027,29 +9201,29 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties View admin notes - + Bekijk adminnotities Error - + Foutmelding This user does not exist. - + Deze gebruiker bestaat niet. You are being ignored by %1 and can't see their games. - + Je wordt genegeerd door %1 en je kan diens spellen niet zien. Could not get %1's games. - + Kon %1's spellen niet ophalen. @@ -9103,7 +9277,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Failed to get admin notes. - + Ontvangen adminnotities mislukt. @@ -9326,142 +9500,152 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserInterfaceSettingsPage - + General interface settings Algemene weergave-instellingen - + &Double-click cards to play them (instead of single-click) - Dubbel-klik op kaarten om ze te spelen (in plaats van enkele klik) + Dubbelklik op kaarten om ze te spelen (in plaats van enkele klik) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Klikken speelt alle geselecteerde kaarten (in plaats van allen de geklikte kaart) - + &Play all nonlands onto the stack (not the battlefield) by default Speel standaard alle niet-landen op de stapel (niet op het slagveld) - + Do not delete &arrows inside of subphases - + Verwijder &pijlen niet binnen subphases - + Close card view window when last card is removed - + Sluit kaartenweergavevenster wanner laatste kaart verwijderd is - + Auto focus search bar when card view window is opened - + Focus zoekbalk onmiddelijk wanneer kaartenweergavevenster is geopent - + Annotate card text on tokens Maak aantekeningen bij de kaarttekst op tokens - + + Show selection counter during drag selection + Geef selectieteller weer tijdens sleepselectie + + + + Show total selection counter + Geef totale selectieteller weer + + + Use tear-off menus, allowing right click menus to persist on screen Gebruik tear-off menus, laat rechtermuisknopmenu's op het scherm staan - + Notifications settings Notificatie instellingen - + Enable notifications in taskbar Activeer meldingen in de taakbalk - + Notify in the taskbar for game events while you are spectating Meld spelgebeurtenissen in de taakbalk terwijl je toekijkt - + Notify in the taskbar when users in your buddy list connect Notificatie in de taakbalk wanneer gebruikers in je buddy lijst verbinding maken - + Animation settings Animatie-instellingen - + &Tap/untap animation &Tap/Untap animatie - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - - - Use visual deck storage in game lobby - - - - - Use selection animation for Visual Deck Storage - - - - - When adding a tag in the visual deck storage to a .txt deck: - - - - - do nothing - - - - - ask to convert to .cod - - - always convert to .cod - + Deck editor/storage settings + Deckbewerker/opslag instellingen - Default deck editor type - + Open deck in new tab by default + Deck standaard in niewe tab openen - Classic Deck Editor - + Use visual deck storage in game lobby + Visuele deckopslag gebruiken in spelontvangstscherm - Visual Deck Editor - - - - - Replay settings - + Use selection animation for Visual Deck Storage + Gebruik selectieanimatie voor Visuele Deckopslag + When adding a tag in the visual deck storage to a .txt deck: + Wanner een tag aan een .txt deck wordt toegevoegd in de visuele deckopslag: + + + + do nothing + niks doen + + + + ask to convert to .cod + vragen om naar .cod om te zetten + + + + always convert to .cod + altijd omzetten naar .cod + + + + Default deck editor type + Standaard deckbewerker soort + + + + Classic Deck Editor + Klassieke Deckbewerker + + + + Visual Deck Editor + Visuele Deckbewerker + + + + Replay settings + Replayinstellingen + + + Buffer time for backwards skip via shortcut: @@ -9525,23 +9709,24 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9568,25 +9753,108 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9594,22 +9862,32 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9645,7 +9923,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9653,19 +9931,19 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9673,27 +9951,37 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9707,52 +9995,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9760,56 +10018,64 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Klik en sleep om de sorteervolgorde binnen de groepen aan te passen - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9817,17 +10083,17 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9843,47 +10109,52 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9984,133 +10255,133 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties WndSets - + Move selected set to the top Verplaats de geselecteerde set naar boven - + Move selected set up Verplaats de geselecteerde set naar boven - + Move selected set down Verplaats de geselecteerde set naar beneden - + Move selected set to the bottom Verplaats de geselecteerde set naar beneden - + Search by set name, code, or type Zoeken op setnaam, -code of -type - + Default order Standaard volgorde - + Restore original art priority order Herstel oorspronkelijke volgorde prioriteit afbeeldingen - + Enable all sets Alle sets inschakelen - + Disable all sets Alle sets uitschakelen - + Enable selected set(s) Geselecteerde set(s) inschakelen - + Disable selected set(s) Geselecteerde set(s) uitschakelen - + Deck Editor - Deck Editor + Deckbewerker - + Use CTRL+A to select all sets in the view. Gebruik CTRL+A om alle sets in de lijst te selecteren. - + Only cards in enabled sets will appear in the card list of the deck editor. - Alleen kaarten in de ingeschakelde sets zullen verschijnen in de kaartlijst van de deck editor. + Alleen kaarten in de ingeschakelde sets zullen verschijnen in de kaartlijst van de deckbewerker. - + Image priority is decided in the following order: De prioriteit voor afbeeldingen wordt in de volgende volgorde vastgesteld: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki eerst de CUSTOM Map (%1), daarna de Ingeschakelde Sets in deze dialoog (Boven naar Beneden) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Afbeelding - + How to use custom card art Hoe aangepaste kaart afbeeldingen te gebruiken - + Hints Tips - + Note Opmerking - + Sorting by column allows you to find a set while not changing set priority. Sorteren op kolom stelt u in staat om een set te vinden zonder de ingestelde prioriteit te wijzigen. - + To enable ordering again, click the column header until this message disappears. - Om de orderen weer mogelijk te maken, klikt u op de kop van de kolom totdat dit bericht verdwijnt. + Om rangschikken weer mogelijk te maken, klik op de kop van de kolom totdat dit bericht verdwijnt. - + Use the current sorting as the set priority instead Gebruik de huidige sortering als de ingestelde prioriteit - + Sorts the set priority using the same column Sorteert de prioriteit van sets met behulp van dezelfde kolom - + Manage sets Beheer sets @@ -10118,72 +10389,72 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Schudden bij afsluiten - + pile view Stapelweergave @@ -10218,9 +10489,9 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - + Deck Editor - Deck Editor + Deckbewerker @@ -10299,7 +10570,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - + Replays Replays @@ -10346,7 +10617,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Start a Local Game... - Start een Lokaal Spel... + Start een Plaatselijk Spel... @@ -10451,9 +10722,9 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - + Reset Layout - Reset Lay-out + Reset Indeling @@ -10488,7 +10759,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Load Local Deck... - Lokaal Deck Inladen... + Plaatselijk Deck Inladen... @@ -10872,8 +11143,9 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - Toggle Untap - Untap Omschakelen + Toggle Skip Untapping + Toggle Untap + @@ -10892,98 +11164,102 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties + Play Card, Face Down + + + + Attach Card... Kaart Bevestigen Aan... - + Unattach Card Kaart Losmaken - + Clone Card Copieer Kaart - + Create Token... Maak Token... - + Create All Related Tokens Maak Alle Gerelateerde Tokens - + Create Another Token Maak Nog een Token - + Set Annotation... Zet Annotatie... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library Onderkant van Library - + - - + + Exile Exile - + - + Graveyard Graveyard - + Hand Hand - - + + Top of Library Bovenkant van Library - - + Battlefield, Face Down Speelveld, Gesloten @@ -11019,234 +11295,246 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties - + Stack Stack - + Graveyard (Multiple) Graveyard (Meerdere) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Exile (Meerdere) - + + + Exile (Multiple), Face Down + + + + Stack Until Found Stack Totdat Gevonden - + Draw Bottom Card Trek Onderste Kaart - + Draw Multiple Cards from Bottom... Trek Meerdere Kaarten van Onderkant... - + Draw Arrow... Trek Pijl... - + Remove Local Arrows - Verwijder Lokale Pijlen + Verwijder Plaatselijke Pijlen - + Leave Game Verlaat Spel - + Concede Opgeven - + Roll Dice... Werp Dobbelstenen... - + Shuffle Library Library Schudden - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Trek een Kaart - + Draw Multiple Cards... Trek Meerdere Kaarten... - + Undo Draw Kaart Trekken Ongedaan Maken - + Always Reveal Top Card Altijd Bovenste Kaart Onthullen - + Always Look At Top Card Altijd Bovenste Kaart Bekijken - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Draai Weergave Met de Klok Mee - + Rotate View Counterclockwise Draai Weergave Tegen de Klok In - + Unfocus Text Box Textvak Verlaten - + Focus Chat Focus Naar Chat - + Clear Chat Wis Chat - + Refresh Verversen - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause Start/Stop - + Toggle Fast Forward Versnelling Schakelen - + Home - + Visual Deck Storage - + Deck Storage - + Server Server - + Account Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index 0085d6aae..236eff991 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Błąd - + Could not create themes directory at '%1'. Nie można stworzyć folderu motywów w '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Ustawienia motywu - + Current theme: Obecny motyw: - + Open themes folder Otwórz folder motywów - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings Ustawienia menu - + Show keyboard shortcuts in right-click menus Pokazuj skróty klawiszowe w menu kontekstowym - + Show game filter toolbar above list in room tab - + Card rendering Rendering kart - + Display card names on cards having a picture Wyświetl nazwy kart na kartach z obrazkami - + Auto-Rotate cards with sideways layout Automatycznie obracaj karty horyzontalne - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Podbijaj dodatki z których karty są w talii. - + Scale cards on mouse over Skaluj karty po najechaniu kursorem myszy - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand Minimalny procent zachodzenia kart na siebie na stosie oraz w ręce pionowej - + Maximum initial height for card view window: Maksymalna podstawowa wysokość okna podglądu karty - - + + rows rzędy - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Układ ręki - + Display hand horizontally (wastes space) Wyświetl karty na ręce poziomo (marnuje miejsce) - + Enable left justification Wyrównuj karty do lewej strony pola - + Table grid layout Układ stołu - + Invert vertical coordinate Odwróć współrzędne pionowe - + Minimum player count for multi-column layout: Minimalna liczba graczy dla widoku wielokolumnowego - + Maximum font size for information displayed on cards: Maksymalny rozmiar czcionki dla informacji wyświetlanych na kartach @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorSettingsPage - - + + Update Spoilers Uaktualnij Spoilery - - + + Success Sukces - + Download URLs have been reset. Pobrane URL zostały zresetowane. - + Downloaded card pictures have been reset. Pobrane obrazy kart zostały zresetowane - + Error Błąd - + One or more downloaded card pictures could not be cleared. Jedno lub więcej pobranych ilustracji kart nie mogło zostać wyczyszczonych. - + Add URL Dodaj URL - - + + URL: URL: - - + + Edit URL Edytuj URL - + Network Cache Size: Rozmiar sieciowej pamięci podręcznej: - + Redirect Cache TTL: TTL przekierowania w pamięci podręcznej: - + How long cached redirects for urls are valid for. Jak długo zapisane przekierowania są ważne. - + Picture Cache Size: Rozmiar Pamięci Podręcznej dla Obrazków: - + Add New URL Dodaj Nowy URL - + Remove URL Usuń URL - + Day(s) Dni - + Updating... Trwa aktualizacja... - + Choose path Wybierz ścieżkę - + URL Download Priority Priorytet Pobierania URL - + Spoilers Spoilery - + Download Spoilers Automatically Pobierz spoilery automatycznie - + Spoiler Location: Lokalizacja spoilera - + Last Change Ostatnia zmiana - + Spoilers download automatically on launch Spoilery są pobierane automatycznie w czasie premiery. - + Press the button to manually update without relaunching Wciśnij przycisk żeby ręcznie uaktualnić bez restartu. - + Do not close settings until manual update is complete Nie zamykaj ustawień dopóki aktualizacja manualna się nie skończy - + Download card pictures on the fly Pobierz ilustracje kart w locie - + How to add a custom URL Jak dodać niestandardowe URL - + Delete Downloaded Images Usuń Pobrane Ilustracje - + Reset Download URLs Resetuj Pobieranie URLi - + On-disk cache for downloaded pictures Pamięć podręczna na dysku dla pobranych obrazków - + In-memory cache for pictures not currently on screen Pamięć podręczna w pamięci fizycznej dla obrazków które nie są na ekranie @@ -1484,32 +1467,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckListModel - + Count Ilość - + Set Dodatek - + Number Ilość - + Provider ID Provider ID - + Card Karta @@ -1545,12 +1528,12 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan Biblioteka poboczna zablokowana - - + + Error Błąd - + The selected file could not be loaded. Wybrany plik nie mógł zostać wczytany. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2885,17 +2868,17 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgLoadDeckFromClipboard - + Load deck from clipboard Wczytaj talię ze schowka - + Error Błąd - + Invalid deck list. Nieprawidłowa lista talii. @@ -2903,43 +2886,43 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Wczytaj talię + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nazwa karty (lub wyrażenie wyszukiwania): - + Number of hits: Ilość trafień: - + Auto play hits Automatycznie graj trafienia - + Put top cards on stack until... Kładź karty z wierzchu na stosie dopóki... - + No cards matching the search expression exists in the card database. Proceed anyways? W bazie kart nie znaleziono kart które spełniają twoje wyrażenie. Kontynuować mimo to? - + Cockatrice Cockatrice - + Invalid filter Nieprawidłowy filtr @@ -3153,12 +3169,12 @@ Twój email zostanie użyty aby zweryfikować twoje konto. DlgSettings - + Unknown Error loading card database W trakcie wczytywania bazy kart wystąpił nieznany błąd - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3175,7 +3191,7 @@ Może istnieć potrzeba uruchomienia Oracle w celu uaktualnienia bazy kart. Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database version is too old. This can cause problems loading card information or images @@ -3192,7 +3208,7 @@ Zwykle można temu zaradzić poprzez uruchomienie narzędzia Oracle i uaktualnie Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3205,7 +3221,7 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - + File Error loading your card database. Would you like to change your database location setting? @@ -3214,7 +3230,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3223,7 +3239,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3236,59 +3252,59 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - - - + + + Error Błąd - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z taliami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z obrazkami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + Settings Ustawienia - + General Ogólne - + Appearance Wygląd - + User Interface Interfejs - + Card Sources Źródło Karty - + Chat Czat - + Sound Dźwięk - + Shortcuts Skróty @@ -3602,67 +3618,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4110,143 +4126,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Resetuj wszystkie ścieżki - + All paths have been reset Wszystkie ścieżki zostały zresetowane - - - - - - - + + + + + + + Choose path Wybierz ścieżkę - + Personal settings Ustawienia osobiste - + Language: Język: - + Paths (editing disabled in portable mode) Scieżki (edytowanie wyłączone w trybie przenośnym) - + Paths Ścieżki - + How to help with translations - + Decks directory: Katalog z taliami: - + Filters directory: - + Replays directory: Katalog z powtórkami: - + Pictures directory: Katalog z obrazkami: - + Card database: Baza kart: - + Custom database directory: Niestandardowy folder z bazą: - + Token database: Baza tokenów: - + Update channel Aktualizuj kanał - + Check for client updates on startup Sprawdzaj aktualizacje klienta przy uruchomieniu - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Powiadom jeśli właściwości wspierane przez serwer, brakują w kliencie - + Automatically run Oracle when running a new version of Cockatrice Automatycznie uruchom Oracle kiedy uruchomiona jest nowa wersja Cockatrice - + Show tips on startup Pokazuj wskazówki po uruchomieniu - + Last update check on %1 (%2 days ago) @@ -4254,47 +4270,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4302,88 +4318,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4391,52 +4407,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4444,193 +4460,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4732,18 +4768,8 @@ Will now login. Rozpocznij logowanie. - - Number of players - Liczba graczy - - - - Please enter the number of players. - Wprowadź liczbę graczy. - - - - + + Player %1 Gracz %1 @@ -4846,8 +4872,8 @@ Rozpocznij logowanie. - - + + Error Błąd @@ -5254,36 +5280,36 @@ Lokalna wersja to %1, wersja zdalna to %2. Pokaż/Ukryj - + New Version Nowa Wersja - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gratulacje z powodu aktualizacji do Cockatrice %1! Oracle uruchomi się teraz aby zaktualizować bazę danych kart. - + Cockatrice installed Cockatrice zainstalowany - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gratulacje z powodu instalacji Cockatrice %1! Oracle uruchomi się teraz aby zainstalować początkowe bazy danych kart. - + Card database Baza kart - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5292,29 +5318,29 @@ Czy chcesz teraz uaktualnić bazę kart? Jeżeli nie masz pewności lub jesteś nowym użytkownikiem, wybierz "Tak" - - + + Yes Tak - - + + No Nie - + Open settings Otwórz ustawienia - + New sets found Znaleziono nowe dodatki - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5329,17 +5355,17 @@ Kody dodatków: %1 Czy chcesz je teraz uaktywnić? - + View sets Pokaż dodatki - + Welcome Witaj - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5348,64 +5374,64 @@ Wszystkie dodatki w bazie kart zostały aktywowane. Przeczytaj więcej na temat zmiany kolejności czy wyłączania poszczególnych dodatków i tym podobnych w oknie "Zarządzaj Dodatkami". - - + + Information Informacja - + A card database update is already running. Aktualizacja bazy kart jest w trakcie - + Unable to run the card database updater: Nie można uruchomić aktualizacji bazy kart: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5416,55 +5442,55 @@ Prawdopodobnie nie jest to problem, ale ta wiadomośc może oznaczać że jest d Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - - - - - + + + + + Load sets/cards Wczytaj dodatki/karty - + Selected file cannot be found. Wybrany plik nie został znaleziony. - + You can only import XML databases at this time. Możesz zaimportować jedynie bazy danych XML w tym momencie. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Nowe dodatki/karty dodane pomyślnie. Cockatrice ponownie załaduje teraz bazę danych. - + Sets/cards failed to import. Dodatki/karty nie mogły być zaimportowane. - - - + + + Reset Password Zresetuj hasło - + Your password has been reset successfully, you can now log in using the new credentials. Twoje hasło zostały pomyślnie zresetowane, możesz teraz zalogować się używając nowego uwierzytelniania. - + Failed to reset user account password, please contact the server operator to reset your password. Nie udało się zresetować hasła użytkownika, skontaktuj się z operatorem serwera w celu wykonania resetu. - + Activation request received, please check your email for an activation token. Otrzymano żądanie aktywacji, odbierz token aktywujący w swojej skrzynce email. @@ -5515,7 +5541,7 @@ Cockatrice ponownie załaduje teraz bazę danych. ManaBaseWidget - + Mana Base @@ -5669,590 +5695,610 @@ Cockatrice ponownie załaduje teraz bazę danych. MessageLogWidget - + from play z gry - + from their graveyard z jego grobu - + from exile ze strefy wygnania - + from their hand z jego ręki - + the top card of %1's library karta z wierzchu biblioteki gracza %1 - + the top card of their library pierwsza karta jego biblioteki - + from the top of %1's library z wierzchu biblioteki gracza %1 - + from the top of their library z wierzchu jego biblioteki - + the bottom card of %1's library karta z wierzchu biblioteki gracza %1 - + the bottom card of their library ostatnia karta jego biblioteki - + from the bottom of %1's library z dna biblioteki gracza %1 - + from the bottom of their library z dna jego biblioteki - + from %1's library z biblioteki gracza %1 - + from their library z jego biblioteki - + from sideboard z talii pobocznej - + from the stack ze stosu - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 od teraz gra z odwróconą kartą z wierzchu %2. - + %1 is not revealing the top card %2 any longer. %1 już nie gra z odwróconą kartą z wierzchu %2. - + %1 can now look at top card %2 at any time. %1 może teraz patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 no longer can look at top card %2 at any time. %1 nie może już patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 attaches %2 to %3's %4. %1 przyłącza %2 do %4 %3 - + %1 has conceded the game. %1 poddał grę. - + %1 has unconceded the game. %1 nie poddał gry. - + %1 has restored connection to the game. %1 odzyskał połączenie z grą. - + %1 has lost connection to the game. %1 utracił połączenie z grą. - + %1 points from their %2 to themselves. %1 wskazuje z jego karty %2 na samego siebie. - + %1 points from their %2 to %3. %1 wskazuje z jego karty %2 na kartę %3. - + %1 points from %2's %3 to themselves. %1 wskazuje z karty gracza %2 %3 na samą siebie. - + %1 points from %2's %3 to %4. %1 wskazuje z karty gracza %2 %3 na kartę %4. - + %1 points from their %2 to their %3. %1 wskazuje z jego karty %2 na jego kartę %3. - + %1 points from their %2 to %3's %4. %1 wskazuje z jego karty %2 na karty %3's %4. - + %1 points from %2's %3 to their own %4. %1 wskazuje z kart %2's %3 na swoją kartę %4. - + %1 points from %2's %3 to %4's %5. %1 wskazuje z karty gracza %2 %3 na kartę gracza %4 (%5). - + %1 creates a face down token. - + %1 creates token: %2%3. %1 stworzył token: %2%3. - + %1 has loaded a deck (%2). Gracz %1 wczytał talię (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Gracz %1 wczytał talię oraz %2 kart talii pobocznej (hash: %3). - + %1 destroys %2. %1 niszczy %2. - + a card kartę - + %1 gives %2 control over %3. %1 oddaje %3 pod kontrolę gracza %2. - + %1 puts %2 into play%3 face down. %1 zagrywa kartę %2 na pole bitwy%3 koszulką do góry. - + %1 puts %2 into play%3. %1 zagrywa kartę %2 na pole bitwy %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 kładzie %2 %3 na cmentarz. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 przenosi kartę %2 do strefy wygnania %3. - + %1 moves %2%3 to their hand. %1 bierze %2 %3 do ręki. - + %1 puts %2%3 into their library. %1 wkłada %2 %3 do biblioteki. - + %1 puts %2%3 onto the bottom of their library. %1 przenosi %2 %3 na dno swojej biblioteki. - + %1 puts %2%3 on top of their library. %1 przenosi %2 %3 na wierzch swojej bibliotek - + %1 puts %2%3 into their library %4 cards from the top. %1 kładzie %2 %3 na jego biblioteke %4 kart z góry. - + %1 moves %2%3 to sideboard. %1 przenosi kartę %2 %3 do talii pobocznej. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 zagrywa kartę %2 %3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 próbuje dobrać z pustej biblioteki - + %1 draws %2 card(s). %1 dobiera %2 kartę.%1 dobiera %2 kart.%1 dobiera %2 kart.%1 dobiera %2 kart. - + %1 is looking at %2. %1 przegląda %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 kładzie %2 grzbietem do góry. - + %1 turns %2 face-up. %1 kładzie %2 grzbietem do dołu. - + The game has been closed. Gra została zakończona. - + The game has started. Gra została rozpoczęta. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 dołączył do gry. - + %1 is now watching the game. %1 obserwuje grę. - + You have been kicked out of the game. Wyrzucono cię z gry. - + %1 has left the game (%2). %1 opuścił grę (%2). - + %1 is not watching the game any more (%2). %1 przestał oglądać grę (%2). - + %1 is not ready to start the game any more. %1 nie jest już gotowy do rozpoczęcia gry. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 tasuje swoją talie i dobiera nową rękę z %2 kartą.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami. - + %1 shuffles their deck and draws a new hand. %1 tasuje swoją talie i dobiera nową rękę. - + You are watching a replay of game #%1. Oglądasz powtórkę gry #%1. - + %1 is ready to start the game. %1 jest gotowy do rozpoczęcia gry. - + cards an unknown amount of cards karty - + %1 card(s) a card for singular, %1 cards for plural jedną kartę%1 kart%1 kart%1 kart - + %1 lends %2 to %3. %1 pożycza %2 graczowi %3. - + %1 reveals %2 to %3. %1 pokazuję kartę %2 graczowi %3. - + %1 reveals %2. %1 pokazuję kartę %2. - + %1 randomly reveals %2%3 to %4. %1 pokazuje losowo kartę %2 %3 graczowi %4. - + %1 randomly reveals %2%3. %1 pokazuje losowo kartę %2 %3. - + %1 peeks at face down card #%2. %1 spogląda na kartę odwróconą koszulką do góry #%2. - + %1 peeks at face down card #%2: %3. %1 spogląda na kartę odwróconą koszulką do góry #%2: %3. - + %1 reveals %2%3 to %4. %1 pokazuje kartę %2 %3 graczowi %4. - + %1 reveals %2%3. %1 pokazuje kartę %2 %3. - + %1 reversed turn order, now it's %2. %1 odwrócona kolejność tur, teraz %2 - + reversed odwrócone - + normal normalne - + Heads Orzeł - + Tails Reszka - + %1 flipped a coin. It landed as %2. %1 podrzucił monetą. Wylądowała na %2. - + %1 rolls a %2 with a %3-sided die. %1 rzuca %2 kostką %3-ścienną. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 rzuca %2 monetami. Wypadło %3 orłów i %4 reszek. - + %1 rolls a %2-sided dice %3 times: %4. %1 rzuca %2-ścienną kostką %3 razy: %4. - + %1's turn. Tura gracza %1 - + %1 sets annotation of %2 to %3. %1 ustala adnotację karty %2 na %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 ustawia licznik %2 na %3 (%4%5). - + %1 sets %2 to not untap normally. %1 blokuje karcie %2 normalne odtapowywanie. - + %1 sets %2 to untap normally. %1 umożliwia karcie %2 normalne odtapowywanie. - + %1 removes the PT of %2. %1 usuwa PT %2. - + %1 changes the PT of %2 from nothing to %4. %1 zmienia PT %2 z niczego na %4 - + %1 changes the PT of %2 from %3 to %4. %1 zmienia PT %2 z %3 na %4. - + %1 has locked their sideboard. %1 zablokował jego bibliotekę poboczną - + %1 has unlocked their sideboard. %1 odblokował jego bibliotekę poboczną - + %1 taps their permanents. %1 tapuje swoje permanenty. - + %1 untaps their permanents. %1 odtapowuje swoje permanenty. - + %1 taps %2. %1 tapuje kartę %2. - + %1 untaps %2. %1 odtapowuje kartę %2. - + %1 shuffles %2. %1 tasuje %2. - + %1 shuffles the bottom %3 cards of %2. %1 tasuje %3 dolne karty z %2. - + %1 shuffles the top %3 cards of %2. %1 tasuje %3 górne karty z %2. - + %1 shuffles cards %3 - %4 of %2. %1 tasuje %3 - %4 z %2 kart. - + %1 unattaches %2. %1 odczepia kartę %2. - + %1 undoes their last draw. %1 cofa ostatnią dobraną kartę - + %1 undoes their last draw (%2). %1 cofa ostatnią dobraną kartę (%2) @@ -6260,110 +6306,110 @@ Cockatrice ponownie załaduje teraz bazę danych. MessagesSettingsPage - + Word1 Word2 Word3 Wyraz1 Wyraz2 Wyraz3 - + Add New Message Dodaj Nową Wiadomość - + Edit Message Edytuj Wiadomość - + Remove Message Usuń Wiadomość - + Add message Dodaj wiadomość - - + + Message: Wiadomość: - + Edit message Edytuj wiadomość - + Chat settings Ustawienia czatu - + Custom alert words Niestandardowy tekst alertu. - + Enable chat mentions Włącz wywołania na czacie - + Enable mention completer Włącz autouzupełnianie wywołania - + In-game message macros Makra wiadomości w trakcie gry - + How to use in-game message macros Jak korzystać z makr wiadomości w trakcie gry - + Ignore chat room messages sent by unregistered users Ignoruj wiadomości na czacie od niezarejestrowanych użytkowników - + Ignore private messages sent by unregistered users Ignoruj prywatne wiadomości od niezarejestrowanych użytkowników - - + + Invert text color Odwróć kolor tekstu - + Enable desktop notifications for private messages Włącz desktopowe notyfikacje dla prywatnych wiadomości - + Enable desktop notification for mentions Włącz desktopowe notyfikacje dla wywołań - + Enable room message history on join Włącz historię wiadomości w pokoju po dołączeniu - - + + (Color is hexadecimal) (Kolor w kodzie heksadecymalnym) - + Separate words with a space, alphanumeric characters only Rozdziel słowa spacją, tylko alfanumeryczne znaki @@ -6376,32 +6422,37 @@ Cockatrice ponownie załaduje teraz bazę danych. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6543,57 +6594,57 @@ Cockatrice ponownie załaduje teraz bazę danych. PhasesToolbar - + Untap step Krok odtapowania - + Upkeep step Krok utrzymania - + Draw step Krok dobierania - + First main phase Pierwsza faza główna - + Beginning of combat step Krok początku walki - + Declare attackers step Krok deklaracji atakujących - + Declare blockers step Krok deklaracji broniących - + Combat damage step Krok obrażeń bitewnych - + End of combat step Krok zakończenia walki - + Second main phase Druga faza główna - + End of turn step Krok końca tury @@ -6610,134 +6661,138 @@ Cockatrice ponownie załaduje teraz bazę danych. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6745,48 +6800,65 @@ Cockatrice ponownie załaduje teraz bazę danych. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing Przypnij wersję - + Unpin Printing Odepnij wersję - + Show Related cards Pokaż powiązane karty @@ -6835,17 +6907,25 @@ Cockatrice ponownie załaduje teraz bazę danych. Data wydania - - + + Descending Malejąco - + Ascending Rosnąco + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6984,6 +7064,33 @@ Cockatrice ponownie załaduje teraz bazę danych. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7132,37 +7239,37 @@ Cockatrice ponownie załaduje teraz bazę danych. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7205,6 +7312,14 @@ Cockatrice ponownie załaduje teraz bazę danych. Gry + + SayMenu + + + S&ay + + + SequenceEdit @@ -7269,53 +7384,53 @@ Cockatrice ponownie załaduje teraz bazę danych. ShortcutSettingsPage - - + + Restore all default shortcuts Przywróć domyślne skróty - + Do you really want to restore all default shortcuts? Czy na pewno chcesz przywrócić wszystkie domyślne skróty? - + Clear all default shortcuts Wyczyść wszystkie domyślne skróty - + Do you really want to clear all shortcuts? Czy na pewno chcesz wyczyścić wszystkie skróty? - + Section: Sekcja: - + Action: Akcja: - + Shortcut: Skrót: - + How to set custom shortcuts Jak ustawić niestandardowe skróty klawiszowe - + Clear all shortcuts Wyczyść wszystkie skróty - + Search by shortcut name Szukaj po nazwie skrótu @@ -7384,27 +7499,27 @@ Sprawdź ustawienia skrótów! SoundSettingsPage - + Enable &sounds Włącz &efekty dźwiękowe - + Current sounds theme: Aktualny zestaw dźwięków: - + Test system sound engine Testuj silnik dźwiękowy systemu - + Sound settings Ustawienia dźwięku - + Master volume Główne ustawienia głośności @@ -7620,59 +7735,123 @@ Sprawdź ustawienia skrótów! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7680,60 +7859,52 @@ Sprawdź ustawienia skrótów! TabDeckEditor - + Card Info Informacje o karcie - + Deck Talia - + Filters Filtry - + &View &Widok - + Card Database - + Printing Wersja - - - - - + Visible Widoczne - - - - - + Floating Pływające - + Reset layout Resetuj układ - + Deck: %1 Talia: %1 @@ -7741,61 +7912,55 @@ Sprawdź ustawienia skrótów! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7860,7 +8025,7 @@ Sprawdź ustawienia skrótów! - + New folder Nowy folder @@ -7942,18 +8107,18 @@ Wprowadź nazwę: Czy na pewno chcesz usunąć wybrane pliki? - + Delete remote decks Usuń zdalną talię - + Are you sure you want to delete the selected decks? Czy na pewno chcesz usunąć wybrane talie? - + Name of new folder: Nazwa nowego folderu: @@ -7971,12 +8136,12 @@ Wprowadź nazwę: - + Error - + Could not open deck at %1 @@ -8025,197 +8190,191 @@ Wprowadź nazwę: TabGame - - - + + + Replay Powtórka - - + + Game Gra - - + + Player List Lista Graczy - - + + Card Info Informacje o karcie - - + + Messages Wiadomości - - + + Replay Timeline Oś czasu powtórki - + &Phases &Fazy - + &Game &Gra - + Next &phase Następna &faza - + Next phase with &action Następna faza z akcją - + Next &turn Następna &tura - + Reverse turn order Odwróć kolejność tur - + &Remove all local arrows Usuń wszystkie &wskaźniki - + Rotate View Cl&ockwise Obróć widok &zgodnie z ruchem wskazówek zegara - + Rotate View Co&unterclockwise Obróć widok &przeciwnie do ruchu wskazówek zegara - + Game &information &Informacje o grze - + Un&concede Powróć do gry - - - + + + &Concede &Poddaj grę - + &Leave game &Opuść grę - + C&lose replay &Zamknij powtórkę - + &Focus Chat Skup czat - + &Say: &Powiedz: - + Selected cards - + &View &Widok - - - - + Visible Widoczne - - - - + Floating Pływające - + Reset layout Resetuj układ - + Concede Poddaj grę - + Are you sure you want to concede this game? Czy na pewno chcesz poddać tę grę? - + Unconcede Powróć do gry - + You have already conceded. Do you want to return to this game? Poddałeś już się. Czy chcesz powrócić do tej gry? - + Leave game Opuść grę - + Are you sure you want to leave this game? Czy na pewno chcesz zakończyć tę rozgrywkę? - + A player has joined game #%1 Gracz dołączył do gry #%1 - + %1 has joined the game %1 dołączył(a) do gry. - + You have been kicked out of the game. Wyrzucono cię z gry. @@ -9315,142 +9474,152 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserInterfaceSettingsPage - + General interface settings Ogólne ustawienia interfejsu - + &Double-click cards to play them (instead of single-click) Zagrywaj karty po&dwójnym kliknięciem (zamiast pojedynczym) - + &Clicking plays all selected cards (instead of just the clicked card) Kliknięcie zagrywa wszystkie wybrane karty (zamiast tylko klikniętej karty) - + &Play all nonlands onto the stack (not the battlefield) by default Karty nie będące lądami zagrywaj domyślnie na stos (zamiast na &pole bitwy) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Dodaj tekst karty na tokenach - - - Use tear-off menus, allowing right click menus to persist on screen - Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie - - - - Notifications settings - Ustawienia powiadomień - - - - Enable notifications in taskbar - Włącz &powiadomienia na pasku zadań - - - - Notify in the taskbar for game events while you are spectating - Powiadomienia na pasku zadań dla gier, które &obserwujesz - - - - Notify in the taskbar when users in your buddy list connect - Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - - - - Animation settings - Ustawienia animacji - - - - &Tap/untap animation - Animacja &tapowania/odtapowania - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - Domyślnie otwórz talię w nowej karcie - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ustawienia powiadomień + + + + Enable notifications in taskbar + Włącz &powiadomienia na pasku zadań - do nothing - + Notify in the taskbar for game events while you are spectating + Powiadomienia na pasku zadań dla gier, które &obserwujesz + + + + Notify in the taskbar when users in your buddy list connect + Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - ask to convert to .cod - + Animation settings + Ustawienia animacji + + + + &Tap/untap animation + Animacja &tapowania/odtapowania - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + Domyślnie otwórz talię w nowej karcie - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings Ustawienia powtórek - + Buffer time for backwards skip via shortcut: Buforuj czas do przeskoku w tył wywołane przez skrót: @@ -9514,23 +9683,24 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9557,25 +9727,108 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9583,22 +9836,32 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9634,7 +9897,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9642,19 +9905,19 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9662,27 +9925,37 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9696,52 +9969,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9749,56 +9992,64 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9806,17 +10057,17 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9832,47 +10083,52 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9973,133 +10229,133 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. WndSets - + Move selected set to the top Przesuń zaznaczony set do góry - + Move selected set up Przesuć wybrany dodatek w górę - + Move selected set down Przesuń wybrany dodatek w dół - + Move selected set to the bottom Przesuń zaznaczony set na dół - + Search by set name, code, or type Szukaj po nazwie dodatku, kodzie lub typie - + Default order Domyślna kolejność - + Restore original art priority order Przywróć oryginalny priorytet obrazów kart - + Enable all sets Włącz wszystkie dodatki - + Disable all sets Wyłącz wszystkie dodatki - + Enable selected set(s) Włącz zaznaczony/e set(y) - + Disable selected set(s) Wyłącz zaznaczony/e set(y) - + Deck Editor Edycja Talii - + Use CTRL+A to select all sets in the view. Użyj CTRL+A aby wybrać wszystkie dodatki w widoku. - + Only cards in enabled sets will appear in the card list of the deck editor. Tylko karty z odblokowanych dodatków pojawią się w edytorze talii kart - + Image priority is decided in the following order: Priorytet obrazów jest ustalany w następujący sposób: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki najpierw folder NIESTANDARDOWYCH (%1), potem Włączone Dodatki w tym oknie dialogowym (Z góry na dół) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Obraz karty - + How to use custom card art Jak ustawić niestandardowe obrazy do kart - + Hints Wskazówki - + Note Notatka - + Sorting by column allows you to find a set while not changing set priority. Sortowane kolumnami pozwala ci znaleźć dodatek nie zmieniając ich priorytetów. - + To enable ordering again, click the column header until this message disappears. Aby wyłączyć sortowanie klikaj ponownie na ten sam nagłówek kolumny aż zniknie ten komunikat. - + Use the current sorting as the set priority instead Użyj tymczasowego sortowania jako priorytet dodatków - + Sorts the set priority using the same column Sortuje priorytet dodatków korzystając z tej samej kolumny - + Manage sets Zarządzaj dodatkami @@ -10107,72 +10363,72 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Niepogrupowane - + Group by Type Grupuj po typie - + Group by Mana Value Grupuj po wartości many - + Group by Color Grupuj po kolorze - + Unsorted Nieposortowane - + Sort by Name Sortuj według nazwy - + Sort by Type Sortuj według typu - + Sort by Mana Cost Sortuj według wartości many - + Sort by Colors Sortuj według koloru - + Sort by P/T Sortuj według P/T - + Sort by Set Sortuj według dodatku - + shuffle when closing przetasuj przy zamykaniu - + pile view widok sterty @@ -10207,7 +10463,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - + Deck Editor Edycja Talii @@ -10288,7 +10544,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - + Replays Powtórki @@ -10440,7 +10696,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - + Reset Layout Zresetuj wygląd @@ -10861,8 +11117,9 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - Toggle Untap - Odtapuj + Toggle Skip Untapping + Toggle Untap + @@ -10881,98 +11138,102 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. + Play Card, Face Down + + + + Attach Card... Dołącz Kartę.. - + Unattach Card Odłącz Kartę - + Clone Card Sklonuj Kartę - + Create Token... Utwórz token… - + Create All Related Tokens Stwórz Wszystkie Pokrewne Tokeny - + Create Another Token Stwórz Kolejny Token - + Set Annotation... Ustaw Adnotację… - + Select All Cards in Zone Zaznacz Wszystkie Karty w Strefie - + Select All Cards in Row Zaznacz Wszystkie Karty w Rzędzie - + Select All Cards in Column Zaznacz Wszystkie Karty w Kolumnie - + Reveal Selected Cards to All Players - - + + Bottom of Library Dno biblioteki - + - - + + Exile Wygnanie - + - + Graveyard Grób - + Hand Ręka - - + + Top of Library Wierzch biblioteki - - + Battlefield, Face Down Pole Bitwy, Koszulką do Góry @@ -11008,234 +11269,246 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. - + Stack Stos - + Graveyard (Multiple) Cmentarz (Wiele) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Wygnanie (Wiele) - + + + Exile (Multiple), Face Down + + + + Stack Until Found Stos aż do Znalezienia - + Draw Bottom Card Dobierz Kartę z Dna - + Draw Multiple Cards from Bottom... Dobierz Wiele Kart z Dna... - + Draw Arrow... Narysuj wskaźnik... - + Remove Local Arrows Usuń Lokalne Wskaźniki - + Leave Game Opuść Grę - + Concede Poddanie - + Roll Dice... Rzuć Kośćmi... - + Shuffle Library Potasuj Bibliotekę - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Dobierz Kartę - + Draw Multiple Cards... Dobierz Wiele Kart... - + Undo Draw Cofnij Dobranie - + Always Reveal Top Card Zawsze Odkrywaj Górną Kartę - + Always Look At Top Card Zawsze Patrz na Górną Kartę - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Obróć widok zgodnie z ruchem wskazówek zegara - + Rotate View Counterclockwise Obróć widok przeciwnie do ruchu wskazówek zegara - + Unfocus Text Box Nie skupiaj Tekstu - + Focus Chat Skup Czat - + Clear Chat Wyczyść czat - + Refresh Odśwież - + Skip Forward Przeskocz do Przodu - + Skip Backward Przeskocz do Tyłu - + Skip Forward by a lot Bardzo Przeskocz do Przodu - + Skip Backward by a lot Bardzo Przeskocz do Tyłu - + Play/Pause Odtwórz/Pauza - + Toggle Fast Forward Przełącz przyspieszenie - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index c2624e6d5..f67c8d138 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Definições do Tema - + Current theme: Tema actual: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Rendering da carta - + Display card names on cards having a picture Mostrar o nome em cartas com imagem - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Aumentar as cartas ao passar o rato - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Disposição da Mão - + Display hand horizontally (wastes space) Mostrar mão horizontalmente (desperdiça espaço) - + Enable left justification Permitir justificação de texto à esquerda - + Table grid layout Esquema da mesa - + Invert vertical coordinate Inverter coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de kogadores para layout com múltiplas colunas: - + Maximum font size for information displayed on cards: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckListModel - + Count - + Set - + Number Número - + Provider ID - + Card Carta @@ -1545,12 +1528,12 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - - + + Error Erro - + The selected file could not be loaded. O ficheiro seleccionado não pôde ser carregado. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Carregar baralho da memória - + Error Erro - + Invalid deck list. Lista de deck inválida. @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Carregar baralho + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar a base de dados das cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3173,7 +3189,7 @@ Poderá necessitar de correr o Oracle para actualizar a base de dados. Gostaria de alterar a localização da base de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -3190,7 +3206,7 @@ Pode ser reparado correndo o Oracle de novo para actualizar a base de dados das Gostaria de alterar a localização da base de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3199,7 +3215,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3208,7 +3224,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3217,7 +3233,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3226,59 +3242,59 @@ Would you like to change your database location setting? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O directório do seu deck é inválido. Gostaria de voltar atrás e corrigir o directório? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O directório das imagens das cartas é inválido. Gostaria de voltar atrás e corrigir o directório? - + Settings Definições - + General Geral - + Appearance Aparência - + User Interface Interface do utilizador - + Card Sources - + Chat Chat - + Sound Som - + Shortcuts Atalhos @@ -3591,67 +3607,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4099,143 +4115,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Escolher directório - + Personal settings Defenições pessoais - + Language: Língua: - + Paths (editing disabled in portable mode) Diretórios (edição desativada em modo portátil) - + Paths Directórios - + How to help with translations - + Decks directory: Directório dos decks: - + Filters directory: - + Replays directory: Directório de replays: - + Pictures directory: Directório das imagens: - + Card database: Base de dados das cartas: - + Custom database directory: - + Token database: Base de dados das fichas: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4243,47 +4259,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4291,88 +4307,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4380,52 +4396,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4433,193 +4449,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4719,18 +4755,8 @@ Will now login. Entrará agora. - - Number of players - Número de jogadores - - - - Please enter the number of players. - Por favor introduza o número de jogadores. - - - - + + Player %1 Jogador %1 @@ -4833,8 +4859,8 @@ Entrará agora. - - + + Error Erro @@ -5239,34 +5265,34 @@ Versão local é %1, versão remota é %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Base de dados de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5275,110 +5301,110 @@ Deseja actualizar a sua base de dados agora?%s Se não tem a certeza ou é a primeira vez que está a utilizar o Cockatrice, escolha "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir definições - + New sets found Novas expansões foram encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Ver expansões - + Welcome Bem-vindo/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Informação - + A card database update is already running. A actualização da base de dados das cartas já está a correr. - + Unable to run the card database updater: Incapaz de correr o actualizador da base de dados das cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5386,55 +5412,55 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Carregar expansões/cartas - + Selected file cannot be found. O ficheiro escolhido não foi encontrado. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Actualização completa com sucesso. %s O Cockatrice irá agora recarregar a base de dados das cartas. - + Sets/cards failed to import. Falha na importação de expansões/cartas. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5485,7 +5511,7 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ManaBaseWidget - + Mana Base @@ -5639,590 +5665,610 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo dos seus grimórios - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo dos seus grimórios - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo dos seus grimórios - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo dos seus grimórios - + from %1's library do grimório de %1 - + from their library dos seus grimórios - + from sideboard do sideboard - + from the stack da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está agora a manter a carta do topo %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 já não está a manter a carta do topo %2 revelada. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 restabeleceu a ligação ao jogo. - + %1 has lost connection to the game. %1 perdeu a ligação ao jogo. - + %1 points from their %2 to themselves. %1 aponta do seu %2 para si próprio. - + %1 points from their %2 to %3. %1 aponta do seu %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta do %3 de %2 para si própria. - + %1 points from %2's %3 to %4. %1 aponta de %3 de %2 para %4. - + %1 points from their %2 to their %3. %1 aponta do seu %2 para %3. - + %1 points from their %2 to %3's %4. %1 aponta do seu %2 para o %4 de %3. - + %1 points from %2's %3 to their own %4. %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to %4's %5. %1 aponta de %3 de %2 para %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas na sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card uma carta - + %1 gives %2 control over %3. %1 dá controlo sobre %3 a %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 coloca %2 em jogo %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 coloca %2%3 no seu grimório. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 move %2%3 para a sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 no seu grimório. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo do seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 põe %2%3 no seu grimório, %4 cartas a contar do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 joga %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 está a olhar para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Este jogo foi encerrado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está agora a ver o jogo. - + You have been kicked out of the game. Você foi expulso do jogo. - + %1 has left the game (%2). %1 abandonou o jogo (%2). - + %1 is not watching the game any more (%2). %1 não está mais a observar o jogo (%2). - + %1 is not ready to start the game any more. %1 já não está pronto a começar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Está a ver um replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto a começar o jogo. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoreamente %2%3. a %4. - + %1 randomly reveals %2%3. %1 revela aleatoreamente %2%3. - + %1 peeks at face down card #%2. %1 espreita a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espreita a carta virada para baixo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 atirou uma moeda ao ar. Calhou %2. - + %1 rolls a %2 with a %3-sided die. %1 obteve %2 com um dado de %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 coloca uma nota de %2 em%3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 altera o número de marcadores %2 para %3(%4%5). - + %1 sets %2 to not untap normally. %1 define %2 para não desvirar normalmente. - + %1 sets %2 to untap normally. %1 define %2 para desvirar normalmente. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 bloqueou o seu sideboard. - + %1 has unlocked their sideboard. %1 desbloqueou o seu sideboard. - + %1 taps their permanents. %1 vira as suas permanentes. - + %1 untaps their permanents. %1 desvira as suas permanentes. - + %1 taps %2. %1 vira %2. - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 baralha %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz a sua última compra. - + %1 undoes their last draw (%2). %1 desfaz a sua última compra (%2). @@ -6230,110 +6276,110 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message - + Chat settings Definições do Chat - + Custom alert words Palavras alerta personalizáveis - + Enable chat mentions Permitir menções no chat - + Enable mention completer Permitir complementação de menções - + In-game message macros Macros de mensagem no decorrer do jogo - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensagens de chat enviadas por utilizadores não registados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por utilizadores não registados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Permitir notificações de mensagens privadas no Ambiente de Trabalho - + Enable desktop notification for mentions Permitir notificações de menções no ambiente de trabalho - + Enable room message history on join Permitir o histórico de mensagens de uma sala ao entrar - - + + (Color is hexadecimal) (A cor é hexadecimal) - + Separate words with a space, alphanumeric characters only Palavras separadas com espaço, apenas caracteres alfanuméricos @@ -6346,32 +6392,37 @@ O Cockatrice irá agora recarregar a base de dados das cartas. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6513,57 +6564,57 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PhasesToolbar - + Untap step Etapa de Desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase 1ª Fase Principal (pré-combate) - + Beginning of combat step Etapa de Início de Combate - + Declare attackers step Etapa de Declaração de Atacantes - + Declare blockers step Etapa de Declaração de Bloqueadores - + Combat damage step Etapa de Dano de Combate - + End of combat step Etapa de Fim de Combate - + Second main phase 2ª Fase Principal (pós-combate) - + End of turn step Fase Final @@ -6580,134 +6631,138 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6715,48 +6770,65 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6805,17 +6877,25 @@ O Cockatrice irá agora recarregar a base de dados das cartas. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6954,6 +7034,33 @@ O Cockatrice irá agora recarregar a base de dados das cartas. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7102,37 +7209,37 @@ O Cockatrice irá agora recarregar a base de dados das cartas. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7175,6 +7282,14 @@ O Cockatrice irá agora recarregar a base de dados das cartas. Jogos + + SayMenu + + + S&ay + + + SequenceEdit @@ -7239,53 +7354,53 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7352,27 +7467,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Permitir &sons - + Current sounds theme: Tema sonoro actual: - + Test system sound engine Teste motor de sistema de som - + Sound settings Definições de Som - + Master volume Volume mestre @@ -7588,59 +7703,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7648,60 +7827,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Informação da carta - + Deck Baralho - + Filters Filtros - + &View &Vista - + Card Database - + Printing - - - - - + Visible Visível - - - - - + Floating Flutuando - + Reset layout Repor disposição - + Deck: %1 Deck:%1 @@ -7709,61 +7880,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7828,7 +7993,7 @@ Please check your shortcut settings! - + New folder Nova pasta @@ -7910,18 +8075,18 @@ Por favor introduza um nome: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Nome da nova pasta: @@ -7939,12 +8104,12 @@ Por favor introduza um nome: - + Error - + Could not open deck at %1 @@ -7993,197 +8158,191 @@ Por favor introduza um nome: TabGame - - - + + + Replay Repetição - - + + Game Jogo - - + + Player List Jogadores - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Repetir Cronologia - + &Phases Fa&ses - + &Game &Jogo - + Next &phase Próxima &fase - + Next phase with &action - + Next &turn Próximo &turno - + Reverse turn order - + &Remove all local arrows &Remover todas as setas locais - + Rotate View Cl&ockwise Rodar a vista no se&ntido dos ponteiros - + Rotate View Co&unterclockwise rodar a vista no se&ntido contrário aos ponteiros - + Game &information &Informação do jogo - + Un&concede - - - + + + &Concede &Conceder - + &Leave game Sair do &jogo - + C&lose replay &Fechar replay - + &Focus Chat - + &Say: &Dizer: - + Selected cards - + &View &Vista - - - - + Visible Visível - - - - + Floating Flutuando - + Reset layout Repor disposição - + Concede Conceder - + Are you sure you want to concede this game? Tem a certeza que deseja conceder este jogo? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Tem a certeza que deseja sair deste jogo? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. Foi expulso do jogo. @@ -9279,142 +9438,152 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserInterfaceSettingsPage - + General interface settings Configurações gerais da interface - + &Double-click cards to play them (instead of single-click) Clicar &duas vezes nas cartas para as jogar (ao invés de clicar apenas uma vez) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos os não-terrenos para a pilha (em vez de para o campo de batalha) por definição - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Permitir anotações em tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - Permitir notificações na barra de tarefas - - - - Notify in the taskbar for game events while you are spectating - Notificar eventos de jogos na barra de tarefas - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Configurações de Animações - - - - &Tap/untap animation - Animação de &virar/desvirar - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + Enable notifications in taskbar + Permitir notificações na barra de tarefas + - do nothing + Notify in the taskbar for game events while you are spectating + Notificar eventos de jogos na barra de tarefas + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Configurações de Animações + + + + &Tap/untap animation + Animação de &virar/desvirar - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9478,23 +9647,24 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9521,25 +9691,108 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9547,22 +9800,32 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9598,7 +9861,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9606,19 +9869,19 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9626,27 +9889,37 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9660,52 +9933,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9713,56 +9956,64 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9770,17 +10021,17 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9796,47 +10047,52 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9937,133 +10193,133 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f WndSets - + Move selected set to the top Mover expansão seleccionada para o topo - + Move selected set up Mover expansão seleccionada para cima - + Move selected set down Mover expansão seleccionada para baixo - + Move selected set to the bottom Mover expansão seleccionada para o fundo - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Permitir todas as expansões - + Disable all sets Inactivar todas as expansões - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10071,72 +10327,72 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing baralhar quando terminar - + pile view vista em pilha @@ -10171,7 +10427,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - + Deck Editor Editor de Baralhos @@ -10252,7 +10508,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - + Replays @@ -10404,7 +10660,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - + Reset Layout @@ -10825,7 +11081,8 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10845,98 +11102,102 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile Exílio - + - + Graveyard Cemitério - + Hand Mão - - + + Top of Library - - + Battlefield, Face Down @@ -10972,234 +11233,246 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Desistir - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index 4a2548812..9956c1793 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Atualizar - + Parse Set Name and Number (if available) Analisar nome e número do conjunto (se disponível) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Abrir em uma nova aba - + Are you sure? Você tem certeza? - + The decklist has been modified. Do you want to save the changes? A lista foi modificada. Gostaria de salvar as alterações? - - - - - - + + + + + + Error Erro - + Could not open deck at %1 Não foi possível abrir o deck em %1 - + Could not save remote deck O deck não pode ser salvo remotamente. - - + + The deck could not be saved. Please check that the directory is writable and try again. O deck não pôde ser salvo. Por favor, verifique se a pasta não é somente leitura e tente novamente. - + Save deck Salvar deck - + The deck could not be saved. O deck não pôde ser salvo. - + There are no cards in your deck to be exported Não há cartas em seu deck a serem exportadas @@ -133,196 +133,168 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. AppearanceSettingsPage - + seconds segundos - + Error Erro - + Could not create themes directory at '%1'. Não foi possível criar o diretório de temas em '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Habilitar essa funcionalidade desabilita o uso do Selecionador de Impressões. - -Você não será capaz de gerenciar preferências de impressões por decks e nem ver impressões que outros jogadores escolheram para os decks deles. - -Você deverá usar o Gerenciador de Coleção, disponível via Banco de Dados de Cartas -> Gerenciar Coleções. - -Tem certeza de que quer habilitar essa funcionalidade? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - Confirmar Alteração - - - + Theme settings Configurações de Tema - + Current theme: Tema atual: - + Open themes folder Abra a pasta de temas - + Home tab background source: Fonte do background da tab Home: - + Home tab background shuffle frequency: Frequência de escolha aleatória de background da tab Home: - + Disabled Desabilitado - + + Display card name of background in bottom right: + + + + Menu settings Configurações do Menu - + Show keyboard shortcuts in right-click menus Mostrar atalhos de teclado nos menus de clique com o botão direito - + Show game filter toolbar above list in room tab Mostrar o filtro de jogos acima da lista da tab de sala - + Card rendering Renderização das cartas - + Display card names on cards having a picture Mostrar nome nas cartas que possuem imagem - + Auto-Rotate cards with sideways layout Rotacionar automaticamente cartas com layout horizontal - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Priorizar conjuntos que o deck contém no topo do seletor de impressão - + Scale cards on mouse over Redimensionar cartas ao passar o mouse - + Use rounded card corners Usar cantos de cartas arredondados - + Minimum overlap percentage of cards on the stack and in vertical hand Porcentagem mínima de sobreposição de cartas na pilha e na mão vertical - + Maximum initial height for card view window: Altura inicial máxima para a janela de visualização de cartas: - - + + rows linhas - + Maximum expanded height for card view window: Altura expandida máxima para a janela de visualização de cartas: - + Card counters Marcadores de carta - + Counter %1 Marcador %1 - + Hand layout Organização da mão - + Display hand horizontally (wastes space) Mostrar a mão na horizontal (desperdiça espaço) - + Enable left justification Habilitada justificação à esquerda - + Table grid layout Organização do campo de batalha - + Invert vertical coordinate Inverter a coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de jogadores para o layout de multi-colunas - + Maximum font size for information displayed on cards: Tamanho máximo da fonte para informações nas cartas: @@ -330,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor Abrir no Editor de Decks @@ -650,22 +627,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardInfoPictureWidget - + View related cards Visualizar cartas relacionadas - + Add card to deck Adicionar carta ao deck - + Mainboard Main - + Sideboard Side @@ -701,124 +678,124 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardMenu - + Re&veal to... Revelar para... - + &All players Todos os &jogadores - + View related cards Visualizar cartas relacionadas - + Token: Ficha: - + All tokens Todas as fichas - + &Select All &Marque todos - + S&elect Row Selecionar Linha - + S&elect Column Selecionar Coluna - + &Play &Jogar - + &Hide &Ocultar - + Play &Face Down Jogue &virada para baixo - + &Tap / Untap Turn sideways or back again &Virar / Desvirar - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down &Virar - + &Peek at card face &Olhar a face da carta - + &Clone &Clonar - + Attac&h to card... Ane&xar na carta... - + Unattac&h De&sanexar - + &Draw arrow... &Desenhar seta... - + &Set annotation... Colocar &nota... - + Ca&rd counters Marcadores de carta - + &Add counter (%1) &Adicionar marcador(%1) - + &Remove counter (%1) &Remover marcador (%1) - + &Set counters (%1)... &Definir marcadores (%1)... @@ -834,133 +811,133 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardZoneLogic - + their hand nominative sua mão - + %1's hand nominative mão de %1 - + their library look at zone seu grimório - + %1's library look at zone grimório de %1 - + of their library top cards of zone, do seu grimório - + of %1's library top cards of zone do grimório de %1 - + their library reveal zone seu grimório - + %1's library reveal zone grimório de %1 - + their library shuffle seu grimório - + %1's library shuffle grimório de %1 - - - their library - nominative - seu grimório - - - - %1's library - nominative - grimório de %1 - + their library + nominative + seu grimório + + + + %1's library + nominative + grimório de %1 + + + their graveyard nominative seu cemitério - + %1's graveyard nominative cemitério de %1 - + their exile nominative seu exílio - + %1's exile nominative exílio de %1 - - - their sideboard - look at zone - seu sideboard - - - - %1's sideboard - look at zone - sideboard de %1 - their sideboard - nominative + look at zone seu sideboard %1's sideboard + look at zone + sideboard de %1 + + + + their sideboard + nominative + seu sideboard + + + + %1's sideboard nominative sideboard de %1 - + their custom zone '%1' nominative sua zona customizada '%1' - + %1's custom zone '%2' nominative zona customizada '%2' de %1 @@ -1022,7 +999,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorCardDatabaseDockWidget - + Card Database Banco de Dados de Cartas @@ -1030,7 +1007,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorCardInfoDockWidget - + Card Info Informações da carta @@ -1053,32 +1030,32 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Adicionar ao Sideboard - + Select Printing Escolher Impressão - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards Mostrar cartas Relacionadas - + Add card to &maindeck Adicionar carta ao &baralho principal - + Add card to &sideboard Adicionar carta ao &sideboard @@ -1086,32 +1063,32 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorDeckDockWidget - + Loading Database... - + Banner Card Carta em Destaque - + Main Type - + Mana Cost - + Colors - + Cores - + Select Printing Escolher Impressão @@ -1148,12 +1125,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Group by: - + Agrupar por: Format: - + Formato: @@ -1184,17 +1161,17 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpar todos os filtros - + Delete selected Apagar selecionados @@ -1281,7 +1258,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Load deck from online service... - + Carregar baralho de serviço online... @@ -1317,7 +1294,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorPrintingSelectorDockWidget - + Printing Selector Seletor de Impressão @@ -1325,227 +1302,227 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorSettingsPage - - + + Update Spoilers Atualizar Spoilers - - + + Success Sucesso - + Download URLs have been reset. As URLs de download foram redefinidas. - + Downloaded card pictures have been reset. As imagens de cartas baixadas foram redefinidas. - + Error Erro - + One or more downloaded card pictures could not be cleared. Uma ou mais imagens de cartas baixadas não puderam ser apagadas. - + Add URL Adicionar URL - - + + URL: URL: - - + + Edit URL Editar URL - + Network Cache Size: Tamanho do Cache de Rede: - + Redirect Cache TTL: TTL do Cache de Redirecionamento: - + How long cached redirects for urls are valid for. Por quanto tempo os redirecionamentos em cache para URLs são válidos. - + Picture Cache Size: Tamanho do Cache de Imagens: - + Add New URL Adicionar nova URL - + Remove URL Remover URL - + Day(s) Dia(s) - + Updating... Atualizando... - + Choose path Selecione o caminho - + URL Download Priority Prioridade de download de URL - + Spoilers 'Spoilers' - + Download Spoilers Automatically Baixar 'spoilers' automaticamente - + Spoiler Location: Localização do 'spoiler': - + Last Change Última Alteração - + Spoilers download automatically on launch Baixar automaticamente 'spoilers' ao iniciar - + Press the button to manually update without relaunching Pressione o botão para atualizar manualmente sem reinicializar - + Do not close settings until manual update is complete Não feche as configurações até que a atualização manual termine - + Download card pictures on the fly Baixar imagens de cartas em tempo real - + How to add a custom URL Como adicionar uma URL customizada - + Delete Downloaded Images Deletar imagens baixadas - + Reset Download URLs Resetar URLs de download - + On-disk cache for downloaded pictures Cache em disco para imagens baixadas - + In-memory cache for pictures not currently on screen Cache em memória para imagens que não estão atualmente na tela DeckListHistoryManagerWidget - - - Undo - - - Redo - + Undo + Desfazer - Undo/Redo history - + Redo + Refazer + Undo/Redo history + Histórico Desfazer/Refazer + + + Click on an entry to revert to that point in the history. - + [redo] - + [refazer] - + [undo] - + [desfazer] DeckListModel - + Count Contagem - + Set Conjunto - + Number Número - + Provider ID ID do Provedor - + Card Card @@ -1553,12 +1530,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckLoader - + Common deck formats (%1) Formatos de decks comuns (%1) - + All files (*.*) Todos arquivos (*.*) @@ -1624,7 +1601,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Edit default tags - + Editar tags padrão @@ -1655,94 +1632,94 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckPreviewWidget - + Banner Card Carta em Destaque - + Open in deck editor Abrir no editor de decks - + Edit Tags Editar Tags - + Rename Deck Renomear Deck - + Save Deck to Clipboard Salvar deck na área de transferência - + Annotated Anotado - + Annotated (No set info) Anotado (Sem informação de expansão) - + Not Annotated Não Anotado - + Not Annotated (No set info) Não Anotado (Sem informação de expansão) - + Rename File Renomear Arquivo - + Delete File Deletar Arquivo - + Set Banner Card Escolher Carta em Destaque - - + + New name: Novo nome: - - + + Error Erro - + Rename failed Falha ao renomear - + Delete file Deletar Arquivo - + Are you sure you want to delete the selected file? Tem certeza de que deseja excluir o arquivo selecionado? - + Delete failed Falha ao deletar @@ -1752,12 +1729,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Rename deck to "%1" from "%2" - + Renomear baralho de "%1" para "%2" Updated comments (was %1 chars, now %2 chars) - + Comentários atualizados (eram %1 caracteres, agora são %2 caracteres) @@ -1767,42 +1744,42 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Tags changed - + Tags alteradas Set format to %1 - + Definir formato para %1 - + Added (%1): %2 (%3) %4 - + Adicionado (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Movido para %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + Removido "%1" (todas as cópias) - + %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) - + Added - + Adicionado - + Removed - + Removido @@ -1834,12 +1811,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Load from clipboard... - + Carregado da área de transferência... Load from website... - + Carregado do website... @@ -1867,29 +1844,30 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Sideboard fechado - - + + Error Erro - + The selected file could not be loaded. O arquivo selecionado não pode ser carregado - + Deck is greater than maximum file size. - + O baralho é maior que o tamanho máximo de arquivo. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Tem certeza que deseja forçar o início? +Isso irá expulsar todos os jogadores não preparados do jogo. - + Cockatrice Cockatrice @@ -2150,12 +2128,12 @@ Deseja converter o deck para .cod? Open decklists in lobby - + Abrir lista de baralhos no lobby Create game as judge - + Criar jogo como juíz @@ -2291,53 +2269,53 @@ Deseja converter o deck para .cod? Edit Tags - + Editar Tags Add - + Adicionar Confirm - + Confirmar Cancel - + Cancelar Enter a tag and press Enter - + Insira a tag e aperte Enter ✖ - + Invalid Input - + Valor Inválido Tag name cannot be empty! - + Nomes de tags não podem ser vazios! Duplicate Tag - + Tag Duplicada This tag already exists. - + Essa tag já existe. @@ -2384,17 +2362,17 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditDeckInClipboard - + Edit deck in clipboard Editar deck na área de transferência - + Error Erro - + Invalid deck list. Decklist inválida. @@ -2623,7 +2601,7 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp Hide games not created by buddies Hide games not created by buddy - + Esconder jogos não criados por amigos @@ -2895,17 +2873,17 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgLoadDeckFromClipboard - + Load deck from clipboard Carregar deck da área de transferência - + Error Erro - + Invalid deck list. Lista de deck inválida. @@ -2913,43 +2891,43 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + Carregar Baralho de Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Erro de rede: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2968,40 +2946,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Carregar deck + + DlgLocalGameOptions + + + Players: + Jogadores: + + + + General + Geral + + + + Starting life total: + Vida inicial total: + + + + Game setup options + + + + + Remember settings + Lembrar configurações + + + + Local game options + Opções de jogo local + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nome da carta (ou expressões de busca): - + Number of hits: Número de resultados: - + Auto play hits Reproduzir resultados automaticamente - + Put top cards on stack until... Colocar as cartas do topo na pilha até... - + No cards matching the search expression exists in the card database. Proceed anyways? Nenhuma carta correspondente à expressão de busca existe no banco de dados de cartas. Deseja continuar assim mesmo? - + Cockatrice Cockatrice - + Invalid filter Filtro inválido @@ -3122,12 +3133,12 @@ Seu e-mail será utilizado para verificar sua conta. Unmodified Cards: - + Cartas não modificadas: Modified Cards: - + Cartas Modificadas: @@ -3163,12 +3174,12 @@ Seu e-mail será utilizado para verificar sua conta. DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar bando de dados de cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3185,7 +3196,7 @@ Você pode precisar rodar o Oracle novamente para atualizar o banco de dados de Você gostaria de mudar o local da configuração de seu banco de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -3202,7 +3213,7 @@ Usualmente isto pode ser consertado rodando novamente o Oracle para atualizar se Você gostaria de alterar o local da configuração de seu banco de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3215,7 +3226,7 @@ Por favor, envie um ticket para https://github.com/Cockatrice/Cockatrice/issues Gostaria de mudar a definição da localização da sua base de dados? - + File Error loading your card database. Would you like to change your database location setting? @@ -3224,7 +3235,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de seu banco de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3233,7 +3244,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de banco de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3246,59 +3257,59 @@ Por favor, crie um ticket em https://github.com/Cockatrice/Cockatrice/issues Você gostaria de alterar seu local de configuração do banco de dados? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de decks é inválido. Você gostaria de voltar e corrigir o caminho? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de imagens de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + Settings Configurações - + General Geral - + Appearance Aparência - + User Interface Interface do usuário - + Card Sources Fonte de Cartas - + Chat Conversa - + Sound Som - + Shortcuts Atalhos @@ -3308,7 +3319,7 @@ Você gostaria de alterar seu local de configuração do banco de dados? Card Update Check - + Checar Atualização de Carta @@ -3340,7 +3351,7 @@ You can always change this behavior in the 'General' settings tab. Don't run this time - + Não executar desta vez @@ -3537,7 +3548,7 @@ Talvez você tenha que baixar a nova versão manualmente. Copy to clipboard - + Copiar para área de transferência @@ -3555,126 +3566,126 @@ Talvez você tenha que baixar a nova versão manualmente. Criteria: - + Critério: Card Name - + Nome da Carta Type - + Tipo Subtype - + Subtipo Mana Value - + Valor de mana Exactness: - + Exatidão At least - + Pelo menos Exactly - + Exatamente Quantity (N): - + Quantidade (N): Cards drawn (M): - + Cartas compradas (M): cards - + cartas DrawProbabilityWidget - + Draw Probability - + Probabilidade de compra - + Probability of drawing - - - Card Name - - - - - Type - - - Subtype - + Card Name + Nome da Carta - Mana Value - + Type + Tipo + + + + Subtype + Subtipo - At least - + Mana Value + Valor de Mana - - Exactly - + + At least + Pelo menos + Exactly + Exatamente + + + card(s) having drawn at least - + cards - + cartas - + Category - + Categoria - + Qty - + Qtd - + Odds (%) - + Chances (%) @@ -3705,7 +3716,7 @@ Talvez você tenha que baixar a nova versão manualmente. Card Market - + Mercado de Cartas @@ -3759,7 +3770,7 @@ Talvez você tenha que baixar a nova versão manualmente. Budget - + Orçamento @@ -3767,7 +3778,7 @@ Talvez você tenha que baixar a nova versão manualmente. Combos - + Combos @@ -3796,7 +3807,7 @@ Talvez você tenha que baixar a nova versão manualmente. Confirm Delete - + Confirmar Exclusão @@ -3806,12 +3817,12 @@ Talvez você tenha que baixar a nova versão manualmente. Delete Failed - + Falha na Exclusão Failed to delete filter '%1'. - + Falha ao remover filtro '%1'. @@ -3824,17 +3835,17 @@ Talvez você tenha que baixar a nova versão manualmente. player left the game - + jogador saiu do jogo player disconnected from server - + jogador desconectado do servidor reason unknown - + motivo desconhecido @@ -3910,7 +3921,7 @@ Talvez você tenha que baixar a nova versão manualmente. Join Game as Judge - + Entrar no Jogo como Juíz @@ -4120,143 +4131,143 @@ Talvez você tenha que baixar a nova versão manualmente. GeneralSettingsPage - + Reset all paths Redefinir todos os diretórios - + All paths have been reset Todos os caminhos para diretórios foram redefinidos - - - - - - - + + + + + + + Choose path Escolher caminho - + Personal settings Configurações pessoais - + Language: Idioma: - + Paths (editing disabled in portable mode) Caminhos (edição desabilitada no modo portátil) - + Paths Caminhos - + How to help with translations Como ajudar com as traduções - + Decks directory: Pasta de decks: - + Filters directory: - + Replays directory: Pasta de replays: - + Pictures directory: Pasta de imagens: - + Card database: Banco de dados de cartas: - + Custom database directory: Diretório do banco de dados de cartas customizadas - + Token database: Banco de dados de fichas: - + Update channel Canal de atualização - + Check for client updates on startup Verificar atualizações do cliente ao iniciar - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Notificar se uma função suportada pelo servidor está faltando em meu programa. - + Automatically run Oracle when running a new version of Cockatrice Rodar Oracle automaticamente quando rodar uma nova versão de Cockatrice - + Show tips on startup Exibir dicas ao iniciar - + Last update check on %1 (%2 days ago) @@ -4264,47 +4275,47 @@ Talvez você tenha que baixar a nova versão manualmente. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4312,88 +4323,88 @@ Talvez você tenha que baixar a nova versão manualmente. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4401,52 +4412,52 @@ Talvez você tenha que baixar a nova versão manualmente. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4454,193 +4465,213 @@ Talvez você tenha que baixar a nova versão manualmente. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4740,18 +4771,8 @@ Will now login. Irá entrar agora. - - Number of players - Número de jogadores - - - - Please enter the number of players. - Por favor, entre o número de jogadores. - - - - + + Player %1 Jogador %1 @@ -4854,8 +4875,8 @@ Irá entrar agora. - - + + Error Erro @@ -5263,36 +5284,36 @@ A versão local é %1 e a versão remota é %2. Mostrar/Ocultar - + New Version Nova Versão - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Parabéns por atualizar o Cockatrice %1! Oracle será inicializado agora para atualizar sua base de cartas - + Cockatrice installed Cockatrice instalado - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Parabéns pela instalação do Cockatrice %1! O Oracle agora será iniciado para instalar o banco de dados inicial de cartas. - + Card database Base de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5301,29 +5322,29 @@ Gostaria de atualizar seu banco de dados de cartas agora? Se estiver em dúvida ou for um novo usuário selecione "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir configurações - + New sets found Novas expansões encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5336,17 +5357,17 @@ Códigos das coleções: %1 Deseja ativá-las? - + View sets Visualizar expansões - + Welcome Bem vindo - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5355,64 +5376,64 @@ Todas as expansões do banco de dados de cartas foram habilitadas. Leia mais sobre mudar a ordem das expansões ou desabilitando expansões específicas na aba "Gerenciar Expansões". - - + + Information Informação - + A card database update is already running. Uma atualização da base de dados de cartas já está em andamento. - + Unable to run the card database updater: Não foi possível executar a atualização da base de dados de cartas. - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Falha ao começar. O arquivo pode não existir ou as permissões estão incorretas. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5423,55 +5444,55 @@ Provavelmente não é um problema, mas esta mensagem pode significar que existe Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - - - - - + + + + + Load sets/cards Carregar expansões/cards - + Selected file cannot be found. Arquivo selecionado não pode ser encontrado - + You can only import XML databases at this time. Você só pode importar bancos de dados XML neste momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. As novas cartas/coleções foram adicionadas com sucesso. O Cockatrice reiniciará imediatamente o banco de dados de cartas. - + Sets/cards failed to import. Falha ao importar expansões/cartas. - - - + + + Reset Password Redefinir Senha - + Your password has been reset successfully, you can now log in using the new credentials. Sua senha foi alterada com sucesso, você pode se conectar usando suas novas credenciais - + Failed to reset user account password, please contact the server operator to reset your password. Falha ao redefinir a senha da conta do usuário, entre em contato com o operador do servidor para redefinir sua senha. - + Activation request received, please check your email for an activation token. Solicitação de ativação recebida, verifique seu e-mail para um token de ativação. @@ -5522,7 +5543,7 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ManaBaseWidget - + Mana Base @@ -5676,590 +5697,610 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo do seu grimório - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo do seu grimório - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo do seu grimório - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo do seu grimório - + from %1's library do grimório de %1 - + from their library do seu grimório - + from sideboard vindo do sideboard - + from the stack vindo da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está mantendo as carta do topo %2 reveladas. - + %1 is not revealing the top card %2 any longer. %1 não está mais revelando as cartas do topo %2. - + %1 can now look at top card %2 at any time. %1 agora pode olhar a carta do topo de %2 a qualquer momento. - + %1 no longer can look at top card %2 at any time. %1 não pode mais olhar a carta do topo de %2 a qualquer momento. - + %1 attaches %2 to %3's %4. %1 vincula %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. %1 desfez a concessão do jogo - + %1 has restored connection to the game. %1 restaurou a conexão com o jogo. - + %1 has lost connection to the game. %1 perdeu conexão com o jogo. - + %1 points from their %2 to themselves. %1 aponta de sua %2 para si - + %1 points from their %2 to %3. %1 aponta de sua %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta da %3 de %2 para si - + %1 points from %2's %3 to %4. %1 aponta da %3 de %2 para %4 - + %1 points from their %2 to their %3. %1 aponta da sua %2 para sua %3 - + %1 points from their %2 to %3's %4. %1 aponta de sua %2 para %4 de %3 - + %1 points from %2's %3 to their own %4. %1 aponta da %3 de %2 para seu próprio %4. - + %1 points from %2's %3 to %4's %5. %1 aponta da %3 de %2 para a %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria uma ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas no sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card um card - + %1 gives %2 control over %3. %1 dá controle para %2 sobre %3. - + %1 puts %2 into play%3 face down. %1 coloca %2 em jogo%3 com a face voltada para baixo. - + %1 puts %2 into play%3. %1 põe %2 no campo de batalha %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 coloca %2%3 em seu cemitério. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 moveu %2%3 para sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 em seu grimório - + %1 puts %2%3 onto the bottom of their library. %1 coloca %2%3 no fundo de seu grimório. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo de seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 coloca %2%3 do seu grimório %4 cartas abaixo do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 põe %2 na pilha%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tenta comprar de uma biblioteca vazia - + %1 draws %2 card(s). %1 comprou %2 carta(s).%1 comprou %2 carta(s).%1 comprou %2 carta(s). - + %1 is looking at %2. %1 está olhando para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2. - + bottom de fundo - + top de topo - + %1 turns %2 face-down. %1 turnos %2 cartas viradas para baixo. - + %1 turns %2 face-up. %1 turnos %2 cartas viradas para cima. - + The game has been closed. O jogo foi fechado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está assistindo o jogo agora. - + You have been kicked out of the game. Você foi 'chutado' do jogo. - + %1 has left the game (%2). %1 deixou o jogo (%2) - + %1 is not watching the game any more (%2). %1 não está mais assistindo o jogo (%2). - + %1 is not ready to start the game any more. %1 não está mais pronto para inciar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 embaralhou seu grimório e comprou uma nova mão com %2 card.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards. - + %1 shuffles their deck and draws a new hand. %1 embaralhou seu grimório e comprou uma nova mão. - + You are watching a replay of game #%1. Você está assistindo o replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto para inciar o jogo. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta%1 carta(s)%1 carta(s) - + %1 lends %2 to %3. %1 empresta %2 para %3. - + %1 reveals %2 to %3. %1 revela %2 para %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoriamente %2%3 para %4. - + %1 randomly reveals %2%3. %1 revela aleatoriamente %2%3. - + %1 peeks at face down card #%2. %1 olha a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espia a face para baixo da carta #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2 %3 para %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 reverteu a ordem dos turnos, agora é a vez de %2. - + reversed revertido - + normal normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 jogou uma moeda. Saiu %2. - + %1 rolls a %2 with a %3-sided die. %1 tirou %2 em um D%3. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 joga %2 moedas. São %3 caras e %4 coroas. - + %1 rolls a %2-sided dice %3 times: %4. %1 rola um dado de %2 lados %3 vezes: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 altera a anotação de %2 para %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 ajusta o marcador %2 para %3 (%4%5). - + %1 sets %2 to not untap normally. %1 determina que %2 não desvira normalmente. - + %1 sets %2 to untap normally. %1 determina que %2 desvira normalmente. - + %1 removes the PT of %2. %1 remove o P/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 alterar o PR de %2 de nada para %4. - + %1 changes the PT of %2 from %3 to %4. %1 alterar o P/R de %2 de %3 para %4. - + %1 has locked their sideboard. %1 bloqueou seu sideboard - + %1 has unlocked their sideboard. %1 desbloqueou seu sideboard - + %1 taps their permanents. %1 vira suas permanentes. - + %1 untaps their permanents. %1 desvira suas permanentes. - + %1 taps %2. %1 vira %2 - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 embaralha %2 - + %1 shuffles the bottom %3 cards of %2. %1 embaralha as %3 cartas do fundo de %2 - + %1 shuffles the top %3 cards of %2. %1 embaralha as %3 cartas do topo de %2 - + %1 shuffles cards %3 - %4 of %2. %1 embaralha as cartas %3 - %4 de %2 - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz sua última compra. - + %1 undoes their last draw (%2). %1 desfaz sua última compra (%2). @@ -6267,110 +6308,110 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palavra1 Palavra2 Palavra3 - + Add New Message Adicionar nova mensagem - + Edit Message Editar mensagem - + Remove Message Remover mensagem - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message Editar mensagem - + Chat settings Configurações de Conversa - + Custom alert words Palavras de alertas customizadas. - + Enable chat mentions Habilitar menções em conversa - + Enable mention completer Habilitar completador de menções. - + In-game message macros Mensagem no jogo - + How to use in-game message macros Como usar macros de mensagens no jogo - + Ignore chat room messages sent by unregistered users Ignorar mensagens na sala enviadas por usuários não registrados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por usuários não registrados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Ativar notificações da área de trabalho para mensagens privadas. - + Enable desktop notification for mentions Habilitar notificações de desktop para menções. - + Enable room message history on join Ativar histórico de mensagens da sala ao entrar - - + + (Color is hexadecimal) (Cor em hexadecimal) - + Separate words with a space, alphanumeric characters only Separar palavras com um espaço, apenas caracteres alfanuméricos @@ -6383,32 +6424,37 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6550,57 +6596,57 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PhasesToolbar - + Untap step Etapa de desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase Primeira fase principal - + Beginning of combat step Etapa de início de combate - + Declare attackers step Etapa de declaracão de atacantes - + Declare blockers step Etapa de declaração de bloqueadores - + Combat damage step Etapa de dano de combate - + End of combat step Etapa de fim de combate - + Second main phase Segunda fase principal - + End of turn step Etapa de fim de combate @@ -6617,134 +6663,138 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6752,48 +6802,65 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference Preferência - + Pin Printing Fixar Impressão - + Unpin Printing Desafixar Impressão - + Show Related cards Mostrar Cartas Relacionadas @@ -6842,17 +6909,25 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Data de Lançamento - - + + Descending Decrescente - + Ascending Crescente + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6991,6 +7066,33 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7139,37 +7241,37 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7212,6 +7314,14 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Jogos + + SayMenu + + + S&ay + + + SequenceEdit @@ -7276,53 +7386,53 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar todos atalhos padrões - + Do you really want to restore all default shortcuts? Você tem certeza que deseja restaurar todos atalhos padrões? - + Clear all default shortcuts Limpar todos os atalhos padrão - + Do you really want to clear all shortcuts? Você realmente quer limpar todos os atalhos? - + Section: Seção: - + Action: Ação: - + Shortcut: Atalho: - + How to set custom shortcuts Como definir atalhos personalizados - + Clear all shortcuts Limpar todos os atalhos - + Search by shortcut name Pesquisar pelo nome do atalho @@ -7391,27 +7501,27 @@ Por favor, verifique suas configurações de atalho! SoundSettingsPage - + Enable &sounds Habilitar &sons - + Current sounds theme: Tema sonoro atual: - + Test system sound engine Testar sistema de som - + Sound settings Configurações de som - + Master volume Volume principal @@ -7627,59 +7737,123 @@ Por favor, verifique suas configurações de atalho! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7687,60 +7861,52 @@ Por favor, verifique suas configurações de atalho! TabDeckEditor - + Card Info Informação da carta. - + Deck Deck - + Filters Filtros - + &View &Visualizar - + Card Database - + Printing Impressão - - - - - + Visible Visível - - - - - + Floating Flutuante - + Reset layout Reiniciar layout - + Deck: %1 Deck: %1 @@ -7748,61 +7914,55 @@ Por favor, verifique suas configurações de atalho! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7867,7 +8027,7 @@ Por favor, verifique suas configurações de atalho! - + New folder Nova pasta @@ -7949,18 +8109,18 @@ Por favor, entre um nome: Tem certeza de que deseja excluir os arquivos selecionados? - + Delete remote decks Excluir decks remotos - + Are you sure you want to delete the selected decks? Tem certeza de que deseja excluir os decks selecionados? - + Name of new folder: Nome da nova pasta: @@ -7978,12 +8138,12 @@ Por favor, entre um nome: Decks Visuais - + Error - + Could not open deck at %1 @@ -8032,197 +8192,191 @@ Por favor, entre um nome: TabGame - - - + + + Replay Replay - - + + Game Jogo - - + + Player List Lista de jogadores. - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Linha do tempo do Replay - + &Phases &Etapas - + &Game &Jogo - + Next &phase Próxima &etapa - + Next phase with &action Próxima etapa com &acao - + Next &turn Próximo &turno - + Reverse turn order Ordem dos turnos revertida - + &Remove all local arrows &Apagar todas as setas locais - + Rotate View Cl&ockwise Girar visualização em sentido h&orário - + Rotate View Co&unterclockwise Girar visualização em sentido a&nti-horário - + Game &information &Informação de jogo - + Un&concede Cancelar Rendição - - - + + + &Concede &Conceder - + &Leave game &Sair do jogo - + C&lose replay &Fechar replay - + &Focus Chat &Focar no chat - + &Say: &Falar: - + Selected cards - + &View &Visualizar - - - - + Visible Visível - - - - + Floating Flutuante - + Reset layout Reiniciar layout - + Concede Conceder - + Are you sure you want to concede this game? Você tem certeza que deseja conceder este jogo? - + Unconcede Defazer concessão - + You have already conceded. Do you want to return to this game? Você já concedeu. Quer retornar a esse jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Você tem certeza que deseja sair deste jogo? - + A player has joined game #%1 Um jogador entrou no jogo #%1 - + %1 has joined the game %1 entrou no jogo - + You have been kicked out of the game. Você foi 'chutado' do jogo. @@ -9324,142 +9478,152 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserInterfaceSettingsPage - + General interface settings Configurações gerais de interface - + &Double-click cards to play them (instead of single-click) &Duplo clique nos cards para jogá-los (ao invés de clique simples) - + &Clicking plays all selected cards (instead of just the clicked card) Clicar reproduz todas as cartas selecionadas (em vez de apenas a carta clicada) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos não terrenos na pilha (não campo de batalha) por padrão - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed Fechar janela de visualização ao remover a última carta - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Anotar texto de card nas fichas - + + Show selection counter during drag selection + + + + + Show total selection counter + + + + Use tear-off menus, allowing right click menus to persist on screen Usar menus tear-off, permitindo que menus de clique com botão direito persistam na tela - + Notifications settings Configurações de Notificações - + Enable notifications in taskbar Habilitar notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar na barra de tarefas para eventos do jogo enquanto você está como espectador - + Notify in the taskbar when users in your buddy list connect Notificar na barra de tarefas quando usuários na sua lista de amigos se conectar - + Animation settings Configurações de animação - + &Tap/untap animation Animação de &virar/desvirar - + Deck editor/storage settings Configurações do editor/armazenamento de decks - + Open deck in new tab by default Abrir deck em uma nova aba por padrão - + Use visual deck storage in game lobby Usar decks visuais no lobby do jogo - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings Configurações de repetição - + Buffer time for backwards skip via shortcut: Tempo de buffer para retroceder usando atalho: @@ -9523,23 +9687,24 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9566,25 +9731,108 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9592,22 +9840,32 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9643,7 +9901,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9651,19 +9909,19 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9671,27 +9929,37 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9705,52 +9973,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9758,56 +9996,64 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9815,17 +10061,17 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9841,47 +10087,52 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9982,133 +10233,133 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você WndSets - + Move selected set to the top Mover o selecionado para o topo - + Move selected set up Mover o selecionado para cima - + Move selected set down Mover o selecionado para baixo - + Move selected set to the bottom Mover o selecionado para o fundo - + Search by set name, code, or type Procurar por expansão, código ou tipo - + Default order Ordem padrão - + Restore original art priority order Restaurar prioridade da ordem de arte original - + Enable all sets Habilitar todas expansões - + Disable all sets Desabilitar todas expansões - + Enable selected set(s) Habilitar expansão/expansões selecionada(s) - + Disable selected set(s) Desabilitar expansão/expansões selecionada(s) - + Deck Editor Editor de Deck - + Use CTRL+A to select all sets in the view. Use CTRL+A para selecionar todas expansões visíveis. - + Only cards in enabled sets will appear in the card list of the deck editor. Apenas cartas em expansões ativadas irão aparecer na lista de cartas do editor de deck. - + Image priority is decided in the following order: Prioridade da imagem é decidida na seguinte ordem: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primeiro a pasta de PERSONALIDADAS (%1), depois as expansões habilitadas neste diálogo (de cima para baixo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte da Carta - + How to use custom card art Como usar arte de carta personalizada - + Hints Dicas - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Classificar por coluna te permite encontrar um set sem mudar a prioridade de sets - + To enable ordering again, click the column header until this message disappears. Para reabilitar a ordenação, clique no cabeçalho da coluna até que esta mensagem desapareça - + Use the current sorting as the set priority instead Usar a classificação atual como a prioridade de sets - + Sorts the set priority using the same column Classificar a prioridade de sets usando a mesma coluna - + Manage sets Gerenciar expansões @@ -10116,72 +10367,72 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Não agrupado - + Group by Type Agrupar por Tipo - + Group by Mana Value Agrupar por Valor de Mana - + Group by Color Agrupar por Cor - + Unsorted Não ordenado - + Sort by Name Ordenar por Nome - + Sort by Type Ordenar por Tipo - + Sort by Mana Cost Ordenar por Custo de Mana - + Sort by Colors Ordenar por Cores - + Sort by P/T Ordenar por P/T - + Sort by Set Ordenar por Conjunto - + shuffle when closing embaralhar quando fechar - + pile view visualização da pilha @@ -10216,7 +10467,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - + Deck Editor Editor de Deck @@ -10297,7 +10548,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - + Replays Repetições @@ -10449,7 +10700,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - + Reset Layout Redefinir layout @@ -10870,8 +11121,9 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - Toggle Untap - Alternar modo de desvirar + Toggle Skip Untapping + Toggle Untap + @@ -10890,98 +11142,102 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você + Play Card, Face Down + + + + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Duplicar Carta - + Create Token... Criar Ficha - + Create All Related Tokens Criar todas as fichas relacionadas - + Create Another Token Criar Outra Ficha - + Set Annotation... Definir anotação - + Select All Cards in Zone Selecionar Todas as Cartas na Zona - + Select All Cards in Row Selecionar Todas as Cartas na Linha - + Select All Cards in Column Selecionar Todas as Cartas na Coluna - + Reveal Selected Cards to All Players - - + + Bottom of Library Fundo do Grimório - + - - + + Exile Exilar - + - + Graveyard Cemitério - + Hand Mão - - + + Top of Library Topo do Grimório - - + Battlefield, Face Down Campo de batalha, Voltada para baixo @@ -11017,234 +11273,246 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você - + Stack Pilha - + Graveyard (Multiple) Cemitério (Vários) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) Exílio (Vários) - + + + Exile (Multiple), Face Down + + + + Stack Until Found Empilhar Até Encontrar - + Draw Bottom Card Comprar carta do fundo - + Draw Multiple Cards from Bottom... Comprar várias cartas do fundo... - + Draw Arrow... Desenhar seta... - + Remove Local Arrows Remover setas locais - + Leave Game Sair do jogo - + Concede Conceder - + Roll Dice... Rolar dados... - + Shuffle Library Embaralhar grimório - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Comprar uma carta - + Draw Multiple Cards... Comprar várias cartas - + Undo Draw Desfazer compra - + Always Reveal Top Card Sempre revelar a carta do topo do grimório - + Always Look At Top Card Sempre olhar para a carta do topo do grimório - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise Rotacionar visão no sentido horário - + Rotate View Counterclockwise Rotacionar visão no sentido anti-horário - + Unfocus Text Box Desfocar caixa de texto - + Focus Chat Focar no chat - + Clear Chat Limpar chat - + Refresh Atualizar - + Skip Forward Avançar - + Skip Backward Retroceder - + Skip Forward by a lot Avançar Bastante - + Skip Backward by a lot Retroceder Bastante - + Play/Pause Reproduzir/Pausar - + Toggle Fast Forward Ativar/Desativar Avanço Rápido - + Home - + Visual Deck Storage Decks Visuais - + Deck Storage Armazenamento de Deck - + Server Servidor - + Account Servidor - + Administration Administração - + Logs Registros diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index 8f062c706..e666bf0de 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh Обновить - + Parse Set Name and Number (if available) Распознать имя сета и номер (если есть) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Открыть в новой вкладке - + Are you sure? Вы уверены? - + The decklist has been modified. Do you want to save the changes? Деклист был изменен. Вы хотите сохранить изменения? - - - - - - + + + + + + Error Ошибка - + Could not open deck at %1 Не удалось открыть колоду по пути «%1» - + Could not save remote deck Не удалось сохранить сетевую колоду - - + + The deck could not be saved. Please check that the directory is writable and try again. Колода не может быть сохранена. Проверьте что директория доступна для записи и попробуйте снова. - + Save deck Сохранить колоду - + The deck could not be saved. Не удалось сохранить колоду. - + There are no cards in your deck to be exported В колоде нет карт для экспорта @@ -133,202 +133,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds секунд - + Error Ошибка - + Could not create themes directory at '%1'. Не удалость создать папку для тем в '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Включение этой опции приведет к невозможности выбора изданий для карт - -Вы не сможете выбирать предпочтительные издания для колод или смотреть какие издания предпочитают другие игроки - -Менеджер изданий будет доступен в меню База карт -> Менеджер изданий - -Уверены что хотите включить эту опцию? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Включение этой опции приведет к возможности выбора изданий для карт - -Вы сможете выбирать предпочтительные издания для колод или смотреть какие издания предпочитают другие игроки - -Также, будет доступен Менеджер Изданий чтобы сортировать порядок изданий в меню выбора изданий (также доступны другие порядки сортировки, такие как алфавитный или по дате выпуска) - -Уверены что хотите выключить эту опцию? - - - - Confirm Change - Подтвердить изменения - - - + Theme settings Настройки темы - + Current theme: Текущая тема: - + Open themes folder Открыть папку с темами - + Home tab background source: Путь до обоев на домашней вкладке: - + Home tab background shuffle frequency: Частота смены обоев: - + Disabled Отключено - + + Display card name of background in bottom right: + + + + Menu settings Настройки меню - + Show keyboard shortcuts in right-click menus Отображать сочетания клавиш в контекстном меню - + Show game filter toolbar above list in room tab - + Card rendering Отрисовка карт - + Display card names on cards having a picture Отображать название карты поверх изображения - + Auto-Rotate cards with sideways layout Автоматически поворачивать карты альбомной ориентации - + Override all card art with personal set preference (Pre-ProviderID change behavior) Перезаписать все арты в соответствии с выбранными изданиями (Pre-ProviderID меняет это поведение) - + Bump sets that the deck contains cards from to the top in the printing selector Перемещать в начало списка выбора издания те выпуски, из которых в колоде есть карты. - + Scale cards on mouse over Увеличивать карты при наведении мыши - + Use rounded card corners Использовать скругленные углы карт - + Minimum overlap percentage of cards on the stack and in vertical hand Минимальный процент наложения карт в стеке и вертикальной руке - + Maximum initial height for card view window: Максимальная начальная высота для окна просмотра карты - - + + rows строки - + Maximum expanded height for card view window: Макс. высота окна для просмотра карт - + Card counters Жетоны на картах - + Counter %1 Жетон %1 - + Hand layout Раскладка руки - + Display hand horizontally (wastes space) Отображать руку горизонтально (требует места) - + Enable left justification Выравнивание по левому краю - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Maximum font size for information displayed on cards: Максимальный размер шрифта для текста карт: @@ -336,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -656,22 +627,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards Посмотреть связанные карты - + Add card to deck Добавить карту в колоду - + Mainboard Колода - + Sideboard Сайд @@ -707,124 +678,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Показать... - + &All players Всем игрокам - + View related cards Посмотреть связанные карты - + Token: Фишка: - + All tokens Все фишки: - + &Select All Выбрать все - + S&elect Row Выбрать строку - + S&elect Column Выбрать колонку - + &Play Разыграть - + &Hide Спрятать - + Play &Face Down Разыграть лицом вниз - + &Tap / Untap Turn sideways or back again Повернуть / Развернуть - - Toggle &normal untapping - Выключить разворот в фазе разворота + + Skip &untapping + - + T&urn Over Turn face up/face down Перевернуть - + &Peek at card face Посмотреть лицевую сторону - + &Clone Клонировать - + Attac&h to card... Прикрепить к карте... - + Unattac&h Открепить - + &Draw arrow... Нарисовать стрелку - + &Set annotation... Добавить заметку - + Ca&rd counters Жетоны на карте - + &Add counter (%1) &Добавить жетон (%1) - + &Remove counter (%1) &Убрать жетон (%1) - + &Set counters (%1)... &Установить жетоны (%1)... @@ -840,133 +811,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative своей руки - + %1's hand nominative %1 руки - + their library look at zone своей библиотеки - + %1's library look at zone библиотеки %1 - + of their library top cards of zone, своей библиотеки - + of %1's library top cards of zone библиотеки %1 - + their library reveal zone своей библиотеки - + %1's library reveal zone библиотеки %1 - + their library shuffle своей библиотеки - + %1's library shuffle библиотеки %1 - - - their library - nominative - своей библиотеки - - - - %1's library - nominative - библиотеки %1 - + their library + nominative + своей библиотеки + + + + %1's library + nominative + библиотеки %1 + + + their graveyard nominative своего кладбища - + %1's graveyard nominative кладбища %1 - + their exile nominative своего изгнания - + %1's exile nominative изгнание %1 - - - their sideboard - look at zone - своего сайда - - - - %1's sideboard - look at zone - сайда %1 - their sideboard - nominative + look at zone своего сайда %1's sideboard + look at zone + сайда %1 + + + + their sideboard + nominative + своего сайда + + + + %1's sideboard nominative сайда %1 - + their custom zone '%1' nominative кастомной зоны '%1' - + %1's custom zone '%2' nominative кастомной зоны '%2' игрока '%1' @@ -1028,7 +999,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1036,7 +1007,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info Информация о карте @@ -1059,32 +1030,32 @@ This is only saved for moderators and cannot be seen by the banned person.Добавить в сайд - + Select Printing Выбрать издание - + Show on EDHRec (Commander) Посмотреть на EDHRec (Командир) - + Show on EDHRec (Card) Посмотреть на EDHRec (карта) - + Show Related cards Посмотреть связанные карты - + Add card to &maindeck Добавить карту в колоду - + Add card to &sideboard Добавить карту в сайд @@ -1092,32 +1063,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card Обложка колоды - + Main Type Тип - + Mana Cost Мана-стоимость - + Colors Цвет - + Select Printing Выбрать издание @@ -1190,17 +1161,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters Фильтры - + &Clear all filters Убрать все фильтры - + Delete selected Удалить выбранное @@ -1323,7 +1294,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector Выбрать издание @@ -1331,166 +1302,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers Обновить спойлеры - - + + Success Успешно - + Download URLs have been reset. Адреса URL загрузки были сброшены. - + Downloaded card pictures have been reset. Загруженные изображения карт были сброшены. - + Error Ошибка - + One or more downloaded card pictures could not be cleared. Одно или более загруженных изображений карт не может быть удалено. - + Add URL Добавить URL - - + + URL: URL: - - + + Edit URL Изменить URL: - + Network Cache Size: Размер сетевого кеша: - + Redirect Cache TTL: TTL кеша перенаправлений: - + How long cached redirects for urls are valid for. Время жизни кеша редиректов - + Picture Cache Size: Размер кеша изображений: - + Add New URL Добавить новый URL - + Remove URL Удалить URL - + Day(s) Дней - + Updating... Обновление... - + Choose path Выбрать путь - + URL Download Priority Приоритет загрузки URL - + Spoilers Спойлеры - + Download Spoilers Automatically Скачивать спойлеры автоматически - + Spoiler Location: Расположение спойлеров: - + Last Change Последние изменения - + Spoilers download automatically on launch Спойлеры скачиваются автоматически при запуске - + Press the button to manually update without relaunching Нажмите, чтобы обновить вручную без перезапуска - + Do not close settings until manual update is complete Не закрывать настройки до завершения ручного обновления - + Download card pictures on the fly Синхронная загрузка изображений карт - + How to add a custom URL Как добавить пользовательский URL - + Delete Downloaded Images Удалить загруженные изображения - + Reset Download URLs Обнулить загруженные URL - + On-disk cache for downloaded pictures Размер кеша для загруженных артов - + In-memory cache for pictures not currently on screen Размер кеша в памяти для загруженных артов @@ -1498,32 +1469,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1531,27 +1502,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count Количество - + Set Издание - + Number Номер - + Provider ID Provider ID - + Card Название @@ -1559,12 +1530,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) Формат колоды (%1) - + All files (*.*) Все файлы (*.*) @@ -1661,94 +1632,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card Обложка - + Open in deck editor Открыть в редакторе колоды - + Edit Tags Редактировать теги - + Rename Deck Переименовать колоду - + Save Deck to Clipboard Сохранить колоду в буфер обмена - + Annotated Аннотировано - + Annotated (No set info) Аннотировано (нет информации об издании) - + Not Annotated Не аннотировано - + Not Annotated (No set info) Не аннотировано (нет информации об издании) - + Rename File Переименовать файл - + Delete File Удалить файл - + Set Banner Card Установить обложку колоды - - + + New name: Новое название: - - + + Error Ошибка - + Rename failed Не удалось переименовать - + Delete file Удалить файл - + Are you sure you want to delete the selected file? Уверены, что хотите удалить выбранный файл? - + Delete failed Не удалось удалить @@ -1781,32 +1752,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1873,30 +1844,30 @@ This is only saved for moderators and cannot be seen by the banned person.Сайдборд заблокирован - - + + Error Ошибка - + The selected file could not be loaded. Выбранный файл не может быть загружен - + Deck is greater than maximum file size. Колода больше максимально возможного размера файла - + Are you sure you want to force start? This will kick all non-ready players from the game. Уверены что хотите принудительно начать? Это кикнет всех неготовых игроков из игры - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard Редактировать колоду из буфера обмена - + Error Ошибка - + Invalid deck list. Неправильный деклист @@ -2902,17 +2873,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Загрузить колоду из буфера - + Error Ошибка - + Invalid deck list. Неверный деклист. @@ -2920,45 +2891,45 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Вставьте ссылку на сайт с деклистом для импорта (Поддерживаются Archidekt, Deckstats, Moxfield, and TappedOut) - - - - - + + + + + Load Deck from Website Загрузить колоду из интернета - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Недоступен парсер для данного источника (Поддерживаются Archidekt, Deckstats, Moxfield, and TappedOut) - + Network error: %1 Ошибка сети: %1 - + Received empty deck data. Получен пустой ответ - + Failed to parse deck data: %1 Ошибка парсинга колоды: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2983,40 +2954,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Загрузить колоду + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Название карты (или поисковый запрос) - + Number of hits: Количество: - + Auto play hits Автоматически разыгрывать найденные карты - + Put top cards on stack until... Поместить вернюю карту в стек, пока... - + No cards matching the search expression exists in the card database. Proceed anyways? В базе данных карт ничего не найдено. Все равно продолжить? - + Cockatrice Cockatrice - + Invalid filter Неправильный фильтр @@ -3178,12 +3182,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Неизвестная ошибка при загрузке базы карт. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3197,7 +3201,7 @@ Cockatrice не может корректно работать с испорче Вы хотите сменить расположение файла базы карт? - + Your card database version is too old. This can cause problems loading card information or images @@ -3211,7 +3215,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3224,7 +3228,7 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - + File Error loading your card database. Would you like to change your database location setting? @@ -3232,7 +3236,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3240,7 +3244,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3253,59 +3257,59 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - - - + + + Error Ошибка - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + General Основные - + Appearance Вид - + User Interface Интерфейс - + Card Sources Источник карт - + Chat Чат - + Sound Звук - + Shortcuts Горячие клавиши @@ -3622,67 +3626,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4130,143 +4134,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Сбросить - + All paths have been reset Все пути были сброшены - - - - - - - + + + + + + + Choose path Путь - + Personal settings Настройки пользователя - + Language: Язык: - + Paths (editing disabled in portable mode) Путь (в портативном режиме редактирование недоступно) - + Paths Пути расположения - + How to help with translations Как помочь с переводом - + Decks directory: Колоды: - + Filters directory: Фильтровать директории: - + Replays directory: Повторы: - + Pictures directory: Изображения карт: - + Card database: База данных карт: - + Custom database directory: Пользовательские сеты: - + Token database: База данных токенов: - + Update channel Обновить канал - + Check for client updates on startup Проверять обновления при запуске - + Check for card database updates on startup Проверять обновления базы карт при запуске - + Don't check Не проверять - + Prompt for update Поиск обновлений - + Always update in the background Всегда обновлять на фоне - + Check for card database updates every Проверять обновления каждые - + days дней - + Notify if a feature supported by the server is missing in my client Предупреждать, если функции, используемые сервером, отсутствуют в этом клиенте. - + Automatically run Oracle when running a new version of Cockatrice Автоматически запускать Oracle в новой версии Cockatrice - + Show tips on startup Показывать советы при запуске клиента - + Last update check on %1 (%2 days ago) Последнее проверка обновлений была %1 (%2 дней назад) @@ -4274,47 +4278,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard Кладбище - + &View graveyard Посмотреть кладбище - + &Move graveyard to... Переместить кладбище в... - + &Top of library Верх библиотеки - + &Bottom of library Низ библиотеки - + &All players Всем игрокам - + &Hand Рука - + &Exile Изгнание - + Reveal random card to... Показать случайно карту... @@ -4322,88 +4326,88 @@ You may have to manually download the new version. HandMenu - + &Hand Рука - + &View hand Посмотреть руку - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... Переместить руку... - + &Top of library Верх библиотеки - + &Bottom of library Низ библиотеки - + &Graveyard Кладбище - + &Exile Изгнание - + &Reveal hand to... Показать руку... - - + + All players - + Reveal r&andom card to... Показать случайную карту... @@ -4411,52 +4415,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck Создать новую колоду - + Browse Decks Посмотреть колоды - + Browse Card Database Посмотреть базу карт - + Browse EDHRec Посмотреть EDHRec - + Browse Archidekt - + View Replays Посмотреть повтор - + Quit Выход - + Connecting... Подключение... - + Connect Подключиться - + Play Играть @@ -4464,193 +4468,213 @@ You may have to manually download the new version. LibraryMenu - + &Library Библиотека - + &View library Посмотреть библиотеку - + View &top cards of library... Посмотреть топдек - + View bottom cards of library... Посмотреть нижнюю карту библиотеки - + Reveal &library to... Показать библиотеку... - + Lend library to... Предоставить доступ к библиотеке... - + Reveal &top cards to... Показать топдек... - + &Top of library... Топдек - + &Bottom of library... Низ библиотеки - + &Always reveal top card &Всегда показывать верхюю карту - + &Always look at top card &Всегда смотреть верхнюю карту - + &Open deck in deck editor Открыть колоду в редакторе - + &Draw card Взять карту - + D&raw cards... Взять карты - + &Undo last draw Отменить последнее взятие карты - + Shuffle Перемешать - + &Play top card Сыграть топдек - + Play top card &face down Сыграть топдек лицом вниз - + Put top card on &bottom Положить топдек на низ библиотеки - + Move top card to grave&yard Положить топдек в кладбище - + Move top card to e&xile Положить топдек в изгнание - + Move top cards to &graveyard... Положить верхние карты в кладбище - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... Положить верхние карты в кладбище - + + Move top cards to exile face down... + + + + Put top cards on stack &until... Поместить вернюю карту в стек, пока... - + Shuffle top cards... Перемешать верхние карты - + &Draw bottom card &Взять нижнюю карту - + D&raw bottom cards... Взять нижнюю карту библиотеки - + &Play bottom card Разыграть нижнюю карту библиотеки - + Play bottom card &face down Разыграть нижнюю карту библиотеки лицом вниз - + Move bottom card to grave&yard Положить нижнюю карту библиотеки в кладбище - + Move bottom card to e&xile Положить нижнюю карту библиотеки в кладбище - + Move bottom cards to &graveyard... Положить нижние карты библиотеки в кладбище - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... Положить нижние карты в изгнание... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top Переместить нижнюю карту на топдек - + Shuffle bottom cards... Перемешать нижние карты... - - + + &All players Все игроки - + Reveal top cards of library Показать верхние карты библиотеки - + Number of cards: (max. %1) Количество карт: (макс. %1) @@ -4752,18 +4776,8 @@ Will now login. Идет соединение. - - Number of players - Количество игроков - - - - Please enter the number of players. - Введите количество игроков. - - - - + + Player %1 Игрок %1 @@ -4866,8 +4880,8 @@ Will now login. - - + + Error Ошибка @@ -5272,144 +5286,144 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database База карт - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice не может загрузить базу карт. Обновить вашу базу карт сейчас? Если Вы не уверены, или впервые запустили программу, нажмите "Да" - - + + Yes Да - - + + No Нет - + Open settings Открыть настройки - + New sets found Найдены новые издания - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Просмотреть издания - + Welcome Добро пожаловать - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Информация - + A card database update is already running. Обновление базы карт уже идет. - + Unable to run the card database updater: Не удалось запустить обновление базы карт: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5420,55 +5434,55 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - - - - - + + + + + Load sets/cards Загрузить издания/карты - + Selected file cannot be found. Выбранный файл не найден. - + You can only import XML databases at this time. На данный момент вы только можете предостовлять базы данных в формате XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Новые издания/карты успешно добавлены. Сейчас Cockatrice перезагрузит базу карт. - + Sets/cards failed to import. Не удалось импортировать издания/карты. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Невозможно сбросить пароль. Пожалуйста, свяжитесь с оператором сервера. - + Activation request received, please check your email for an activation token. Запрос активации получен. Вам отправлен код активации. Пожалуйста, проверьте свой e-mail. @@ -5519,7 +5533,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5673,590 +5687,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play из игры - + from their graveyard из их кладбищ - + from exile из изгнания - + from their hand из их рук - + the top card of %1's library верхняя карта библиотеки %1 - + the top card of their library верхнюю карту своей библиотеки - + from the top of %1's library с верха библиотеки %1 - + from the top of their library с верха своей библиотеки - + the bottom card of %1's library нижняя карта библиотеки %1 - + the bottom card of their library нижнюю карту своей библиотеки - + from the bottom of %1's library Со дна библиотеки %1 - + from the bottom of their library с низа своей библиотеки - + from %1's library из библиотеки %1 - + from their library из своей библиотеки - + from sideboard из сайдборда - + from the stack из стека - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 оставляет верхнюю карту %2 открытой. - + %1 is not revealing the top card %2 any longer. Верхняя карта %2 %1 больше не остается открытой. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 прикрепляет %2 к %4 под контролем %3. - + %1 has conceded the game. %1 сдался. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 восстановил связь с игрой. - + %1 has lost connection to the game. %1 потерял связь с игрой. - + %1 points from their %2 to themselves. %1 указывает с их %2 на себя. - + %1 points from their %2 to %3. %1 указывает с их %2 на %3. - + %1 points from %2's %3 to themselves. %1 указывает с %3 под контролем %2 на себя. - + %1 points from %2's %3 to %4. %1 указывает с %3, контролируемого %2, на %4. - + %1 points from their %2 to their %3. %1 указывает с их %2 на их %3. - + %1 points from their %2 to %3's %4. %1 указывает с их %2 на %4 под контролем %3. - + %1 points from %2's %3 to their own %4. %1 указывает с %3 под контролем %2 на %4, владельцем которого они являются. - + %1 points from %2's %3 to %4's %5. %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 создает фишку: %2%3. - + %1 has loaded a deck (%2). %1 загрузил колоду (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 загрузил колоду с %2 картами из сайдборда (%3). - + %1 destroys %2. %1 уничтожает %2. - + a card карту - + %1 gives %2 control over %3. %1 передает %2 контроль над %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 поместил %2 на поле битвы %3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1 кладет %2%3 на своё кладбище. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 изгоняет %2%3. - + %1 moves %2%3 to their hand. %1 кладет %2%3 в свою руку. - + %1 puts %2%3 into their library. %1 кладет %2%3 в свою библиотеку. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 кладет %2%3 на верх своей библиотеки. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 поместил %2%3 в сайд. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 разыгрывает %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 берет %2 карту.%1 берет %2 карты.%1 берет %2 карт.%1 берет %2 карты. - + %1 is looking at %2. %1 просматривает %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Игра закрыта. - + The game has started. Игра началась. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 присоединился к игре. - + %1 is now watching the game. %1 вошел как зритель. - + You have been kicked out of the game. Вы были отключены от игры - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 пока не готов начать игру. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 тасует колоду и берет новую руку из %2 карты%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Вы просматриваете запись игры #%1. - + %1 is ready to start the game. %1 готов начать игру. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 открывает %2 для %3. - + %1 reveals %2. %1 открывает %2. - + %1 randomly reveals %2%3 to %4. %1 случайным образом открывает %2%3 для %4. - + %1 randomly reveals %2%3. %1 случайным образом открывает %2%3. - + %1 peeks at face down card #%2. %1 указывает на перевернутую рубашкой вверх карту #%2. - + %1 peeks at face down card #%2: %3. %1 указывает на перевернутую рубашкой вверх карту #%2: %3. - + %1 reveals %2%3 to %4. %1 открывает %2%3 для %4. - + %1 reveals %2%3. %1 открывает %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Орел - + Tails Решка - + %1 flipped a coin. It landed as %2. %1 подбросил монету. Выпал(а) %2. - + %1 rolls a %2 with a %3-sided die. %1 выбросил %2 на %3-гранном кубике. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. %1 помечает %2 "%3". - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 разместил %2 "%3" жетон на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 удалил %2 "%3" жетон на %4 (теперь %5).%1 удалил %2 "%3" жетона на %4 (теперь %5).%1 удалил %2 "%3" жетонов на %4 (теперь %5).%1 удалил %2 "%3" жетона на %4 (теперь %5). - + %1 sets counter %2 to %3 (%4%5). %1 устанавливает жетон %2 на %3 (%4%5). - + %1 sets %2 to not untap normally. %2 теперь не разворачивается как обычно (%1). - + %1 sets %2 to untap normally. %2 теперь разворачивается как обычно (%1). - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 заблокировал(а) свой сайдборд. - + %1 has unlocked their sideboard. %1 разблокировал(а) свой сайдборд. - + %1 taps their permanents. %1 поворачивает свои перманенты. - + %1 untaps their permanents. %1 разворачивает свои перманенты. - + %1 taps %2. %1 поворачивает %2. - + %1 untaps %2. %1 разворачивает %2. - + %1 shuffles %2. %1 размешал %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 открепляет %2. - + %1 undoes their last draw. %1 отменяет последние взятия. - + %1 undoes their last draw (%2). %1 отменяет последние взятия (%2). @@ -6264,110 +6298,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Добавить сообщение - - + + Message: Сообщение: - + Edit message - + Chat settings Настройки чата - + Custom alert words Пользовательские предупреждения - + Enable chat mentions Включить упоминания в чате - + Enable mention completer Включить автозавершение упоминаний - + In-game message macros Макросы сообщений игрового чата - + How to use in-game message macros Как использовать внутриигровые макросы для сообщений - + Ignore chat room messages sent by unregistered users Не уведомлять о сообщениях в чате от незарегистрированных пользователей - + Ignore private messages sent by unregistered users Не уведомлять о личных сообщениях от незарегистрированных пользователей - - + + Invert text color Инвертировать цвет текста - + Enable desktop notifications for private messages Включить оповещения о личных сообщениях - + Enable desktop notification for mentions Включить оповещения об упоминаниях - + Enable room message history on join Отображать историю сообщений при присоединении к комнате. - - + + (Color is hexadecimal) (Шестнадцатеричный цвет) - + Separate words with a space, alphanumeric characters only Разделять слова пробелом, только для букв и цифр @@ -6380,32 +6414,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6547,57 +6586,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Шаг разворота - + Upkeep step Шаг поддержки - + Draw step Шаг взятия карты - + First main phase Первая главная фаза - + Beginning of combat step Шаг начала боя - + Declare attackers step Шаг назначения атакующих - + Declare blockers step Шаг назначения блокирующих - + Combat damage step Шаг нанесения повреждений - + End of combat step Шаг завершения боя - + Second main phase Вторая главная фаза - + End of turn step Заключительный шаг @@ -6614,134 +6653,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Взять карты + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards Взять карты с низа библиотеки - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters Установить жетоны @@ -6749,48 +6792,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters &Жетоны + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6839,17 +6899,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6988,6 +7056,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7136,37 +7231,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7209,6 +7304,14 @@ Cockatrice will now reload the card database. Игры + + SayMenu + + + S&ay + + + SequenceEdit @@ -7273,53 +7376,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7386,27 +7489,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Включить &звуки - + Current sounds theme: Звуковая тема: - + Test system sound engine Протестировать системный звук - + Sound settings Настройки звука - + Master volume Громкости @@ -7622,59 +7725,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7682,60 +7849,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Информация о карте - + Deck Колода - + Filters Фильтры - + &View Вид - + Card Database - + Printing - - - - - + Visible Видимый - - - - - + Floating Плавающий - + Reset layout Сбросить слой - + Deck: %1 Колода: %1 @@ -7743,61 +7902,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7862,7 +8015,7 @@ Please check your shortcut settings! - + New folder Новая папка @@ -7944,18 +8097,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Название новой папки: @@ -7973,12 +8126,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -8027,197 +8180,191 @@ Please enter a name: TabGame - - - + + + Replay Запись игры - - + + Game Игра - - + + Player List Список игроков - - + + Card Info Информация о карте - - + + Messages Сообщения - - + + Replay Timeline Полоса прокрутки записи - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Next phase with &action - + Next &turn Следующий &ход - + Reverse turn order - + &Remove all local arrows &Удалить все указатели - + Rotate View Cl&ockwise Повернуть вид по часовой стрелке - + Rotate View Co&unterclockwise Повернуть вид против часовой стрелки - + Game &information Информация &об игре - + Un&concede - - - + + + &Concede &Сдаться... - + &Leave game Покинуть и&гру - + C&lose replay З&акрыть повтор - + &Focus Chat &Установить фокус на чат - + &Say: Ска&зать: - + Selected cards - + &View Вид - - - - + Visible Видимый - - - - + Floating Плавающий - + Reset layout Сбросить слой - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. Вы были отключены от игры @@ -9316,142 +9463,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + &Double-click cards to play them (instead of single-click) &Двойной клик, чтобы разыграть карту (вместо одинарного) - + &Clicking plays all selected cards (instead of just the clicked card) &Клик для разыгрывания всех выбранных карт (вместо только выбранной) - + &Play all nonlands onto the stack (not the battlefield) by default &Помещать все заклинания в стек при разыгрывании (вместо поля боя) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed Закрыть окно просмотра карт когда последняя карта удалена из него - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Изменить текст карты на токенах - + + Show selection counter during drag selection + + + + + Show total selection counter + + + + Use tear-off menus, allowing right click menus to persist on screen Разрешить закрепление контекстных меню на экране - + Notifications settings - + Enable notifications in taskbar Включить оповещения в панели задач. - + Notify in the taskbar for game events while you are spectating Уведомлять об игровых событиях в панели задач, когда вы наблюдаете за игрой - + Notify in the taskbar when users in your buddy list connect Уведомлять при заходе в игру друга - + Animation settings Настройки анимации - + &Tap/untap animation &Анимировать поворот/разворот карты - + Deck editor/storage settings Редактор колод/Настройки хранилища - + Open deck in new tab by default Открывать колоду в новой вкладке - + Use visual deck storage in game lobby Использовать графическое представление карт в лобби игры - + Use selection animation for Visual Deck Storage Использовать анимацию при выборе карты в графическом хранилище - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod всегда конвертировать в .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings - + Buffer time for backwards skip via shortcut: Время отката назад при нажатии шортката @@ -9515,23 +9672,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9558,25 +9716,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9584,22 +9825,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9635,7 +9886,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9643,19 +9894,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9663,27 +9914,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9697,52 +9958,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9750,56 +9981,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9807,17 +10046,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9833,47 +10072,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9974,133 +10218,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Переместить выбранное издание наверх - + Move selected set up Переместить выбранное издание вверх - + Move selected set down Переместить выбранное издание вниз - + Move selected set to the bottom Переместить выбранное издание в конец. - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Включить все издания - + Disable all sets Отключить все издания - + Enable selected set(s) Включить выбранные издания - + Disable selected set(s) Отключить выбранные издания - + Deck Editor Редактор колод - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art Как использовать кастомный арт для карты - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10108,72 +10352,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Перемешать после просмотра - + pile view просмотр вразнобой @@ -10208,7 +10452,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor Редактор колоды @@ -10289,7 +10533,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10441,7 +10685,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10862,7 +11106,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10882,98 +11127,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile Изгнание - + - + Graveyard Кладбище - + Hand Рука - - + + Top of Library - - + Battlefield, Face Down @@ -11009,234 +11258,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card Взять карту с низа библиотеки - + Draw Multiple Cards from Bottom... Взять несколько карт с низа библиотеки... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Сдаться - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Муллиган - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Взять карту - + Draw Multiple Cards... Взять несколько карт - + Undo Draw - + Always Reveal Top Card Всегда показывать верхнюю карту - + Always Look At Top Card Всегда смотреть верхнюю карту - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box Убрать фокус с окна ввода - + Focus Chat Установить фокус на чат - + Clear Chat Очистить чат - + Refresh - + Обновить - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs Логи diff --git a/cockatrice/translations/cockatrice_sr.ts b/cockatrice/translations/cockatrice_sr.ts index 1116a20ae..c04d16f16 100644 --- a/cockatrice/translations/cockatrice_sr.ts +++ b/cockatrice/translations/cockatrice_sr.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab Otvori u novoj kartici - + Are you sure? Da li ste sigurni? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Podešavanja teme - + Current theme: Sadašnja tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering - + Display card names on cards having a picture Pokazuj imena karti na kartama koje imaju sliku - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Raspored ruke - + Display hand horizontally (wastes space) Prikazuj ruku horizontalno (troši prostor) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckListModel - + Count - + Set - + Number Broj - + Provider ID - + Card Karta @@ -1545,12 +1528,12 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - - + + Error Greška - + The selected file could not be loaded. Izabrani fajl nije mogao biti učitan. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard - + Error Greška - + Invalid deck list. @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Učitaj špil + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Nepoznata Greška učitavanja baze podataka karte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3167,7 +3183,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3178,7 +3194,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3187,14 +3203,14 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3203,7 +3219,7 @@ Would you like to change your database location setting? Da li bi ste želeli da promenite vaše podešavanje lokacije baze podataka? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3212,59 +3228,59 @@ Would you like to change your database location setting? - - - + + + Error Greška - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Podešavanja - + General Opšto - + Appearance Izgled - + User Interface Korisnički interfejs - + Card Sources - + Chat Ćaskanje - + Sound Zvuk - + Shortcuts Prečice @@ -3577,67 +3593,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4085,143 +4101,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Izaberi put - + Personal settings Lična podešavanja - + Language: Jezik: - + Paths (editing disabled in portable mode) - + Paths Putevi - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: Baza podataka karata: - + Custom database directory: - + Token database: Baza podataka tokena: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4229,47 +4245,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4277,88 +4293,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4366,52 +4382,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4419,193 +4435,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4696,18 +4732,8 @@ Will now login. - - Number of players - - - - - Please enter the number of players. - - - - - + + Player %1 @@ -4810,8 +4836,8 @@ Will now login. - - + + Error @@ -5212,144 +5238,144 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5357,54 +5383,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5455,7 +5481,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5609,590 +5635,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6200,110 +6246,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6316,32 +6362,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6483,57 +6534,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -6550,134 +6601,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6685,48 +6740,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6775,17 +6847,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6924,6 +7004,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7072,37 +7179,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7145,6 +7252,14 @@ Cockatrice will now reload the card database. + + SayMenu + + + S&ay + + + SequenceEdit @@ -7209,53 +7324,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7322,27 +7437,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7558,59 +7673,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7618,60 +7797,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 @@ -7679,61 +7850,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7798,7 +7963,7 @@ Please check your shortcut settings! - + New folder @@ -7879,18 +8044,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: @@ -7908,12 +8073,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7962,197 +8127,191 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9246,142 +9405,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9445,23 +9614,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9488,25 +9658,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9514,22 +9767,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9565,7 +9828,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9573,19 +9836,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9593,27 +9856,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9627,52 +9900,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9680,56 +9923,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9737,17 +9988,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9763,47 +10014,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9904,133 +10160,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10038,72 +10294,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -10138,7 +10394,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10219,7 +10475,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10371,7 +10627,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10792,7 +11048,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10812,98 +11069,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile Izgnanstvo - + - + Graveyard Groblje - + Hand Ruka - - + + Top of Library - - + Battlefield, Face Down @@ -10939,234 +11200,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index 227983d31..376b9fe5f 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Temainställningar - + Current theme: Aktuellt tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Kortrendering - + Display card names on cards having a picture Visa kortnamn på kort som har bilder - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Skala kort på musen över - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Handlayout - + Display hand horizontally (wastes space) Visa hand horisontellt (slösar plats) - + Enable left justification Aktivera vänsterjustering - + Table grid layout Bordets rutnätlayout - + Invert vertical coordinate Invertera vertikal koordinat - + Minimum player count for multi-column layout: Minst antal spelare för multi-kolumnlayout: - + Maximum font size for information displayed on cards: Maximal teckenstorlek för information som visas på kort: @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorSettingsPage - - + + Update Spoilers Uppdatera Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Fel - + One or more downloaded card pictures could not be cleared. - + Add URL Lägg till webbadress - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Uppdaterar ... - + Choose path Välj sökväg - + URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Ladda ner Spoilers automatiskt - + Spoiler Location: Spoiler Plats: - + Last Change Senaste ändring - + Spoilers download automatically on launch Spoilers laddas ner automatiskt vid lanseringen - + Press the button to manually update without relaunching Tryck på knappen för att manuellt uppdatera utan omstart - + Do not close settings until manual update is complete - + Download card pictures on the fly Ladda enkelt ned bilder på kort - + How to add a custom URL Hur man lägger till en valfri webbadress - + Delete Downloaded Images Ta bort nedladdade bilder - + Reset Download URLs Återställ nedladdnings-webbadresser - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckListModel - + Count - + Set - + Number Nummer - + Provider ID - + Card Kort @@ -1545,12 +1528,12 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - - + + Error Fel - + The selected file could not be loaded. Den valda filen kunde ej laddas. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2885,17 +2868,17 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgLoadDeckFromClipboard - + Load deck from clipboard Ladda lek från urklipp - + Error Fel - + Invalid deck list. Ogiltig leklista. @@ -2903,43 +2886,43 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Ladda lek + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3152,12 +3168,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Ett okänt fel har inträffat vid laddande av kortdatabasen - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3174,7 +3190,7 @@ Möjligen kan du behöva starta om oracle för att uppdatera din kortdatabas. Vill du ändra dina databaslokaliseringsinställningar? - + Your card database version is too old. This can cause problems loading card information or images @@ -3191,7 +3207,7 @@ Detta kan oftast fixas genom att starta om oracle för att uppdatera din kortdat Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3200,7 +3216,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3209,7 +3225,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3218,7 +3234,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3227,59 +3243,59 @@ Would you like to change your database location setting? - - - + + + Error Fel - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + Settings Inställningar - + General Allmänt - + Appearance Utseende - + User Interface Gränssnitt - + Card Sources - + Chat Chatt - + Sound Ljud - + Shortcuts Genvägar @@ -3593,67 +3609,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4101,143 +4117,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Välj sökväg - + Personal settings Personliga inställningar - + Language: Språk: - + Paths (editing disabled in portable mode) Sökvägar (redigering inaktiverad i bärbart läge) - + Paths Sökvägar - + How to help with translations - + Decks directory: Lekkatalog: - + Filters directory: - + Replays directory: Repriskatalog: - + Pictures directory: Bildkatalog: - + Card database: Kortdatabas: - + Custom database directory: - + Token database: Token-databas: - + Update channel Uppdatera kanal - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Meddela om en funktion som stöds av servern saknas i min klient - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Visa tips vid uppstart - + Last update check on %1 (%2 days ago) @@ -4245,47 +4261,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4293,88 +4309,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4382,52 +4398,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4435,193 +4451,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4719,18 +4755,8 @@ Will now login. Inloggning påbörjad. - - Number of players - Antal spelare - - - - Please enter the number of players. - Vänligen ange antal spelare. - - - - + + Player %1 Spelare %1 @@ -4833,8 +4859,8 @@ Inloggning påbörjad. - - + + Error Fel @@ -5238,144 +5264,144 @@ Lokal version är %1, avlägsen version är %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5386,54 +5412,54 @@ Det behöver inte innebära problem men det kan vara ett tecken på att det fin För att uppdatera din klient, gå till Hjälp -> Leta efter uppdateringar. - - - - - + + + + + Load sets/cards Ladda utgåvor/kort - + Selected file cannot be found. Den valda filen kunde inte laddas. - + You can only import XML databases at this time. För tillfället går det bara att importera XML-databaser. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Det gick inte att importera utgåvor/kort. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Det gick inte att återställa kontots lösenord. Kontakta serveroperatören för att få hjälp att återställa lösenordet. - + Activation request received, please check your email for an activation token. @@ -5484,7 +5510,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5638,590 +5664,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile från exil - + from their hand - + the top card of %1's library det översta kortet av %1's lek - + the top card of their library - + from the top of %1's library från toppen av %1's lek - + from the top of their library - + the bottom card of %1's library det sista kortet i %1's lek - + the bottom card of their library - + from the bottom of %1's library från botten av %1's lek - + from the bottom of their library - + from %1's library från %1'a lek - + from their library - + from sideboard från sidbrädan - + from the stack från stapeln - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 håller nu det översta kortet %2 avslöjat. - + %1 is not revealing the top card %2 any longer. %1 håller inte längre det översta kortet %2 avslöjat. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 fäster %2 på %3s %4. - + %1 has conceded the game. - + %1 has unconceded the game. %1 har ångrat att ge upp spelet. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card ett kort - + %1 gives %2 control over %3. %1 ger kontroll över %3 till %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 sätter %2 i spel%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 sätter %2%3 i exil. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 lägger %2%3 i sitt bibliotek %4 kort från toppen. - + %1 moves %2%3 to sideboard. %1 flyttar %2%3 till sidbrädan. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1 spelar %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 vänder %2 nedåt. - + %1 turns %2 face-up. %1 vänder %2 uppåt. - + The game has been closed. Spelet har stängts. - + The game has started. Spelet har börjat. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 kollar nu på spelet. - + You have been kicked out of the game. Du har blivit utsparkad från spelet. - + %1 has left the game (%2). %1 har lämnat spelet (%2) - + %1 is not watching the game any more (%2). %1 tittar inte längre på spelet (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 blandar sin lek och drar en ny hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards kort - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's tur - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6229,110 +6275,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lägg till meddelande - - + + Message: Meddelande: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6345,32 +6391,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6512,57 +6563,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Uptappningssteget - + Upkeep step Underhållssteget - + Draw step Dragsteget - + First main phase Första huvudfasen - + Beginning of combat step Början av stridssteget - + Declare attackers step Attacksteget - + Declare blockers step Blockeringssteget - + Combat damage step Skadesteget - + End of combat step Slutet av stridssteget - + Second main phase Andra huvudfasen - + End of turn step Slutsteget @@ -6579,134 +6630,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6714,48 +6769,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6804,17 +6876,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6953,6 +7033,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7101,37 +7208,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7174,6 +7281,14 @@ Cockatrice will now reload the card database. Spel + + SayMenu + + + S&ay + + + SequenceEdit @@ -7238,53 +7353,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7351,27 +7466,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7587,59 +7702,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7647,60 +7826,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 Lek: %1 @@ -7708,61 +7879,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7827,7 +7992,7 @@ Please check your shortcut settings! - + New folder Ny mapp @@ -7908,18 +8073,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: Namn på den nya mappen: @@ -7937,12 +8102,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7991,197 +8156,191 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Faser - + &Game &Spel - + Next &phase Nästa &fas - + Next phase with &action - + Next &turn Nästa &tur - + Reverse turn order - + &Remove all local arrows Ta &bort alla lokala pilar - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information Spel&information - + Un&concede - - - + + + &Concede &Ge upp - + &Leave game &Lämna spel - + C&lose replay S&täng repris - + &Focus Chat - + &Say: S&äg: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede Ge upp - + Are you sure you want to concede this game? Är du säker på att du vill ge upp detta spel? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Lämna spel - + Are you sure you want to leave this game? Är du säker på att du vill lämna detta spel? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9275,142 +9434,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Allmänna gränssnittsinställningar - + &Double-click cards to play them (instead of single-click) &Dubbelklicka på kort för att spela dem (istället för enkelklick) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Animationsinställningar - - - - &Tap/untap animation - &Tappnings/Upptappningsanimation - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animationsinställningar + + + + &Tap/untap animation + &Tappnings/Upptappningsanimation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9474,23 +9643,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9517,25 +9687,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9543,22 +9796,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9594,7 +9857,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9602,19 +9865,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9622,27 +9885,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9656,52 +9929,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9709,56 +9952,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9766,17 +10017,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9792,47 +10043,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9933,133 +10189,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10067,72 +10323,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing blanda när du stänger - + pile view @@ -10167,7 +10423,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10248,7 +10504,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10400,7 +10656,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10821,7 +11077,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10841,98 +11098,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players + + Bottom of Library + + + + - - + + Exile Exil - + - + Graveyard Kyrkogård - + Hand - - + + Top of Library - - + Battlefield, Face Down @@ -10968,234 +11229,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game Lämna spelet - + Concede Medge - + Roll Dice... Kasta tärning - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card Dra ett kort - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage Visuell Leklagring - + Deck Storage Leklagring - + Server Server - + Account Konto - + Administration Administratör - + Logs Loggar diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts index 6b0d0b9a8..e3c73b94a 100644 --- a/cockatrice/translations/cockatrice_yue.ts +++ b/cockatrice/translations/cockatrice_yue.ts @@ -23,73 +23,73 @@ AbstractDlgDeckTextEdit - + &Refresh - + &刷新 - + Parse Set Name and Number (if available) - + 解析組名及編號(如果有) AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + 你確定嗎? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + 出錯 - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 出現錯誤 - + Could not create themes directory at '%1'. 不能在'%1'創造主題目錄 - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings 主題設定 - + Current theme: 現在主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings 選單設定 - + Show keyboard shortcuts in right-click menus 在右擊選單中顯示鍵盤快速鍵 - + Show game filter toolbar above list in room tab - + Card rendering 卡面算繪 - + Display card names on cards having a picture 最有圖片的牌上顯示牌名 - + Auto-Rotate cards with sideways layout 將打橫佈局卡片自動向上旋轉 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector 將牌庫中包含的牌組推到藝術選擇視窗之頂 - + Scale cards on mouse over 卡牌隨遊標縮放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand 在疊區中和直行手牌中,盡量減少交重疊百分點 - + Maximum initial height for card view window: 卡望框最高開頭高度 - - + + rows 橫列 - + Maximum expanded height for card view window: - + Card counters - + 牌指示物 - + Counter %1 - + 指示物 %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打棟顯示手牌(浪費空間) - + Enable left justification 啟用向左對齊 - + Table grid layout 表格布局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的最少玩家數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字體大小 @@ -322,9 +300,14 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + 返去結果 + + + Open Deck in Deck Editor - + 在套牌編輯&打開牌庫檔案 @@ -332,7 +315,7 @@ Are you sure you would like to disable this feature? Theme - + 卡牌主题 @@ -548,7 +531,7 @@ This is only saved for moderators and cannot be seen by the banned person. Name (Exact) - + 名稱(確切) @@ -608,12 +591,12 @@ This is only saved for moderators and cannot be seen by the banned person. Main Type - + 主要類型 Sub Type - + 副類型 @@ -636,30 +619,30 @@ This is only saved for moderators and cannot be seen by the banned person. View transformation - + 查看轉化 CardInfoPictureWidget - + View related cards 查看關聯卡牌 - + Add card to deck - + 添加卡牌到牌庫 - + Mainboard - + 主牌庫 - + Sideboard - + 副牌庫 @@ -672,12 +655,12 @@ This is only saved for moderators and cannot be seen by the banned person. Set: - + 牌組: Collector Number: - + 收藏號碼: @@ -693,124 +676,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &展示给... - + &All players - + &所有玩家 - + View related cards - + 查看關聯卡牌 - + Token: - + 令牌: - + All tokens - - - - - &Select All - - - - - S&elect Row - - - - - S&elect Column - - - - - &Play - - - - - &Hide - + 所有令牌 - Play &Face Down - + &Select All + &全選 - - &Tap / Untap - Turn sideways or back again - + + S&elect Row + &選擇橫行 + + + + S&elect Column + &選擇直行 - Toggle &normal untapping - + &Play + &使出 + + + + &Hide + &隱藏 + Play &Face Down + &面朝下使出 + + + + &Tap / Untap + Turn sideways or back again + &横置/重置 + + + + Skip &untapping + + + + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,136 +809,136 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + 它們的手牌 - + %1's hand nominative - - - - - their library - look at zone - - - - - %1's library - look at zone - + %1的手牌 - of their library - top cards of zone, - + their library + look at zone + 它們的牌庫 - of %1's library - top cards of zone - + %1's library + look at zone + %1的牌庫 - their library - reveal zone - + of their library + top cards of zone, + 它們的牌庫中 + of %1's library + top cards of zone + %1的牌庫中 + + + + their library + reveal zone + 它們的牌庫 + + + %1's library reveal zone - + %1的牌庫 - + their library shuffle - + 它們的牌庫 - + %1's library shuffle - - - - - their library - nominative - - - - - %1's library - nominative - + %1的牌庫 + their library + nominative + 它們的牌庫 + + + + %1's library + nominative + %1的牌庫 + + + their graveyard nominative - + 它們的墳場 - + %1's graveyard nominative - + %1的的墳場 - + their exile nominative - + 它們的放逐區 - + %1's exile nominative - - - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - + %1的放逐區 their sideboard - nominative - + look at zone + 它們的備牌 %1's sideboard - nominative - + look at zone + %1的備牌 - + + their sideboard + nominative + 它們的備牌 + + + + %1's sideboard + nominative + %1的備牌 + + + their custom zone '%1' nominative - + 它們的自定區域 '%1' - + %1's custom zone '%2' nominative - + %1的自定區域'%2' @@ -1014,7 +997,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽卡牌 - - + + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Downloaded card pictures have been reset. 下載的卡牌圖片已被重置。 - + Error 出現錯誤 - + One or more downloaded card pictures could not be cleared. 一幅或以上的卡牌圖片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Network Cache Size: 圖片網路緩存大小: - + Redirect Cache TTL: 網路緩處再定向生存時間 - + How long cached redirects for urls are valid for. 再定向緩處有效時間 - + Picture Cache Size: 圖片緩存大小: - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Day(s) - + Updating... 更新中... - + Choose path 選擇路徑 - + URL Download Priority URL下載優先次序 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最後變更 - + Spoilers download automatically on launch 開啟時自動下載預覽 - + Press the button to manually update without relaunching 免重新啟動人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能關掉设置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定URL - + Delete Downloaded Images 刪除已下載的圖片 - + Reset Download URLs 重置下載URL - + On-disk cache for downloaded pictures 下載圖片專用硬盤緩儲 - + In-memory cache for pictures not currently on screen 現不在螢光幕上記憶中圖片緩儲 @@ -1484,32 +1467,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count 數目 - + Set 牌組 - + Number 號碼 - + Provider ID 提供者 id - + Card 卡牌 @@ -1545,12 +1528,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ This is only saved for moderators and cannot be seen by the banned person.解鎖鎖上 - - + + Error 出現錯誤 - + The selected file could not be loaded. 選擇的檔案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 從剪貼板載入牌庫 - + Error 出現錯誤 - + Invalid deck list. 牌表不能載入 @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ 載入牌庫 + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名稱(或搜尋表達式) - + Number of hits: 因為cockatrice原生沒有ssl支援, 所以你不能自動下載更新. 請瀏覽到我們的下載網頁,進行人手更新. - + Auto play hits 開關副牌上鎖 - + Put top cards on stack until... 把頂牌放在重疊區, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌資料庫中找不到符合搜尋表達式的卡牌.你想繼續嗎? - + Cockatrice Cockatrice - + Invalid filter 篩選無效 @@ -3153,12 +3169,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 讀取卡牌資料庫時出現不知名錯誤 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3175,7 +3191,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database version is too old. This can cause problems loading card information or images @@ -3192,7 +3208,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3205,7 +3221,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + File Error loading your card database. Would you like to change your database location setting? @@ -3213,7 +3229,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3222,7 +3238,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3238,59 +3254,59 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的資料庫位置設定? - - - + + + Error 出現錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌庫目錄路徑現在無效. 你想要重新設置套牌路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌圖片目錄路徑現在無效. 你想要重新設置卡牌圖片資料庫路徑嗎? - + Settings 設定 - + General 一般 - + Appearance 外觀 - + User Interface 用戶界面 - + Card Sources 卡牌來源 - + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷键 @@ -3604,67 +3620,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4112,143 +4128,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路徑 - + All paths have been reset 重置所有路徑 - - - - - - - + + + + + + + Choose path 選擇路徑 - + Personal settings 個人設定 - + Language: 語言: - + Paths (editing disabled in portable mode) 路徑(在便攜模式下不能編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 牌庫路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌資料庫: - + Custom database directory: 自主資料庫目錄 - + Token database: 令牌資料庫: - + Update channel 更新渠道 - + Check for client updates on startup 啟動時檢查客戶更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客戶端缺少伺服器支持的更功能時,請提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟動新版本Cockatrice時,自動運行Oracle. - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -4256,47 +4272,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4304,88 +4320,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4393,52 +4409,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4446,193 +4462,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4726,18 +4762,8 @@ Will now login. 現在登入. - - Number of players - 玩家人數 - - - - Please enter the number of players. - 請輸入玩家人數 - - - - + + Player %1 玩家 %1 @@ -4840,8 +4866,8 @@ Will now login. - - + + Error 出現錯誤 @@ -5247,36 +5273,36 @@ Local version is %1, remote version is %2. 顯示/隱藏 - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你將Cockatrice升級為%1. 現在會幫你啟動oracle, 更新你的卡牌資料庫. - + Cockatrice installed Cockatrice 已被安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安裝Cockatrice%1. 現在會幫你啟動oracle, 安裝你的卡牌資料庫. - + Card database 卡牌資料庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5285,29 +5311,29 @@ If unsure or first time user, choose "Yes" 如果不確定, 或者是第一次使用,請選擇“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打開設置 - + New sets found 發現新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5316,17 +5342,17 @@ Do you want to enable it/them? 要啟用它們嗎? - + View sets 查看卡组 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5335,64 +5361,64 @@ Read more about changing the set order or disabling specific sets and consequent 請閱讀,了解更多在“卡组设置”對話框裡.更改卡组顺序或者禁用某些卡组的功能. - - + + Information 資訊 - + A card database update is already running. 資料庫更新現已進行. - + Unable to run the card database updater: 不能運行卡牌資料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5404,54 +5430,54 @@ To update your client, go to Help -> Check for Updates. 請到"援助->檢查更新"去更新你的客戶端. - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 你暫時只能導入xml資料庫. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已經成功添加新的卡組/卡牌. Cockatrice會重新載入卡牌資料庫. - + Sets/cards failed to import. 卡組/卡牌導入失敗. - - - + + + Reset Password 重設密碼 - + Your password has been reset successfully, you can now log in using the new credentials. 你的密碼已經成功重置, 你可以用新密碼重新登入。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置用戶密碼失敗, 請聯絡伺服器管責人重置密碼. - + Activation request received, please check your email for an activation token. 已經收到啟動要求, 請從你的電郵獲取啟動驗證碼. @@ -5502,7 +5528,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5656,590 +5682,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 從戰場上 - + from their graveyard 從它們的墳場 - + from exile 從放逐區 - + from their hand 從它們的手牌 - + the top card of %1's library %1牌庫的頂牌 - + the top card of their library 它們的頂牌 - + from the top of %1's library 從%1牌庫的頂 - + from the top of their library 從它們1牌庫頂 - + the bottom card of %1's library %1牌庫的底牌 - + the bottom card of their library 它們牌庫的底牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從它們的牌庫底 - + from %1's library 從%1的牌庫 - + from their library 從它們的牌庫 - + from sideboard 從備牌中 - + from the stack 從疊區中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1現在保持%2頂牌顯現. - + %1 is not revealing the top card %2 any longer. %1不再展示牌庫頂牌%2。 - + %1 can now look at top card %2 at any time. %1可以隨時查看牌庫頂牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以隨時查看牌庫頂牌 %2. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此遊戲宣佈降服. - + %1 has unconceded the game. %1 收回在此遊戲降服. - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 從它們的%2 指向自己。 - + %1 points from their %2 to %3. %1 從它們的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 從%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 從%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 從它們的%2指向它們的%3。 - + %1 points from their %2 to %3's %4. %1 從它們的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 從%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 從%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌庫(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的牌庫(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1將%3的控制轉移給%2. - + %1 puts %2 into play%3 face down. %1將%2%3牌面朝下地放進戰場。 - + %1 puts %2 into play%3. %1將%2放進戰場%3。 - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1將%2%3置入它們的的墳場。 + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1將%2%3移到它們的的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入它們的牌庫。 - + %1 puts %2%3 onto the bottom of their library. %1將%2%3放在它們的牌庫底部。 - + %1 puts %2%3 on top of their library. %1將%2%3放在它們的牌庫頂部。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放在它們的牌庫頂部%4張牌以下。 - + %1 moves %2%3 to sideboard. %1 將%2%3移到副牌庫. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1使出 %2%3。 - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1嘗試從空牌庫抽牌 - + %1 draws %2 card(s). %1抽%2張牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3張卡牌. - + bottom 底部 - + top 頂部 - + %1 turns %2 face-down. %1把%2翻為面朝下。 - + %1 turns %2 face-up. %1把%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 已經不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1將其牌庫洗牌,然後抽%2張牌作為新的起手。 - + %1 shuffles their deck and draws a new hand. %1將其牌庫洗牌,然後抽起新的手牌。 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借給%3. - + %1 reveals %2 to %3. %1把%2展示給%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆轉回合次序,現在輪到%2。 - + reversed 逆轉 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1擲了硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1擲了%2個硬幣。結果為%3個正面和%4個反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子擲了%3次:%4 - + %1's turn. 輪到 %1. - + %1 sets annotation of %2 to %3. %1給%2添加了註釋成%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不被重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防禦. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防禦從無數值轉為%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防禦從%3轉為%4. - + %1 has locked their sideboard. %1已經鎖定它們的的副牌庫。 - + %1 has unlocked their sideboard. %1已經把它們的的副牌庫解鎖。 - + %1 taps their permanents. %1横置它們的永久卡牌 - + %1 untaps their permanents. %1重置它們的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 對 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1 放回它們最後一張抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它們最後一張抽到的卡牌(%2). @@ -6247,110 +6293,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天設定 - + Custom alert words 自定通知觸發詞語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內短信速鍵 - + How to use in-game message macros 如何使用遊戲內短信速鍵 - + Ignore chat room messages sent by unregistered users 不要顯示未註冊用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要顯示未註冊用户的私人消息. - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 容許桌面提醒提及私人消息 - + Enable desktop notification for mentions 容許桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室時開啓消息歷史 - - + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母. @@ -6363,32 +6409,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6530,57 +6581,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step 重置步 - + Upkeep step 維持步 - + Draw step 抓牌步 - + First main phase 第一主階段 - + Beginning of combat step 戰鬥開始步 - + Declare attackers step 宣告阻擋步 - + Declare blockers step 宣告阻擋步 - + Combat damage step 戰鬥損害步 - + End of combat step 戰鬥結束步 - + Second main phase 第二主階段 - + End of turn step 回合完結部 @@ -6597,134 +6648,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6732,48 +6787,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上別針 - + Unpin Printing 版本脫別針 - + Show Related cards 顯示關聯卡牌 @@ -6822,17 +6894,25 @@ Cockatrice will now reload the card database. 發行日期 - - + + Descending 由後到先 - + Ascending 由先倒後 + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6971,6 +7051,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7119,37 +7226,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7192,6 +7299,14 @@ Cockatrice will now reload the card database. 遊戲(眾) + + SayMenu + + + S&ay + + + SequenceEdit @@ -7256,53 +7371,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有常用快鍵嗎? - + Clear all default shortcuts 清除所有常用快鍵 - + Do you really want to clear all shortcuts? 確認清除所有快鍵嗎? - + Section: 部分: - + Action: 行動: - + Shortcut: 快鍵: - + How to set custom shortcuts 如何設置自定快鍵 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快鍵名稱搜尋 @@ -7371,27 +7486,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用&聲效 - + Current sounds theme: 現前聲效主題: - + Test system sound engine 測試系統聲效引擎 - + Sound settings 聲效設置 - + Master volume 主音量 @@ -7607,59 +7722,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7667,60 +7846,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌資訊 - + Deck 牌庫 - + Filters 篩選項目 - + &View &視圖 - + Card Database - + Printing 印刷版本 - - - - - + Visible 可見 - - - - - + Floating 浮動 - + Reset layout 重置布局 - + Deck: %1 牌庫:%1 @@ -7728,61 +7899,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7847,7 +8012,7 @@ Please check your shortcut settings! - + New folder 新文件夾 @@ -7928,18 +8093,18 @@ Please enter a name: 你確定要刪除被選檔案(眾)嗎 ? - + Delete remote decks 刪除伺服器上的牌庫 - + Are you sure you want to delete the selected decks? 你確定要剷除所有被選的牌庫嗎? - + Name of new folder: 新建文件夾名稱: @@ -7957,12 +8122,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -8011,197 +8176,191 @@ Please enter a name: TabGame - - - + + + Replay 遊戲錄像重溫 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + Card Info 卡牌資訊 - - + + Messages 信息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個&階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個&回合 - + Reverse turn order 逆轉回合次序 - + &Remove all local arrows &移除所有自設箭頭 - + Rotate View Cl&ockwise &順時針旋轉視角 - + Rotate View Co&unterclockwise &逆時針旋轉視角 - + Game &information 遊戲&資訊 - + Un&concede 撤銷&投降 - - - + + + &Concede &投降 - + &Leave game &離開遊戲 - + C&lose replay &關閉遊戲錄像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &視圖 - - - - + Visible 可見 - - - - + Floating 浮動 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你確定要宣佈投降嗎? - + Unconcede 撤銷投降 - + You have already conceded. Do you want to return to this game? 你已經宣佈投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定要離開這個遊戲嗎? - + A player has joined game #%1 有玩家加入遊戲 #%1 - + %1 has joined the game %1 已經加入遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -9301,142 +9460,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口設置 - + &Double-click cards to play them (instead of single-click) 雙點擊使出卡牌 (而不是單點擊使出) - + &Clicking plays all selected cards (instead of just the clicked card) &單一點擊把所有選擇的卡牌使出(不只是被點擊的單張卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆疊區(不是戰場) 作為常用設置 - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 在令牌上標註牌中文字 - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 - - - - Notifications settings - 通告設置 - - - - Enable notifications in taskbar - 開啓任務欄通告 - - - - Notify in the taskbar for game events while you are spectating - 觀看時在任務欄提示遊戲信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任務欄中提示好友連到伺服器 - - - - Animation settings - 動畫設定 - - - - &Tap/untap animation - &橫置/重置 動畫 - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - 把用新頁開啟牌庫作為常用設置 - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通告設置 + + + + Enable notifications in taskbar + 開啓任務欄通告 - do nothing - + Notify in the taskbar for game events while you are spectating + 觀看時在任務欄提示遊戲信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任務欄中提示好友連到伺服器 - ask to convert to .cod - + Animation settings + 動畫設定 + + + + &Tap/untap animation + &橫置/重置 動畫 - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + 把用新頁開啟牌庫作為常用設置 - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings 重播設置 - + Buffer time for backwards skip via shortcut: 用速鍵啟動向後跳的緩衝時間: @@ -9500,23 +9669,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9543,25 +9713,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9569,22 +9822,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9620,7 +9883,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9628,19 +9891,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9648,27 +9911,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9682,52 +9955,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9735,56 +9978,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9792,17 +10043,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9818,47 +10069,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9959,133 +10215,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 將選擇的牌組送到最頂 - + Move selected set up 將選擇的牌組往上一格 - + Move selected set down 將選擇的牌組往下一格 - + Move selected set to the bottom 將選擇的牌組送到最底 - + Search by set name, code, or type 用排組名稱,代碼,或類型進行搜尋 - + Default order 常用次序 - + Restore original art priority order 恢復常用藝術先後次序 - + Enable all sets 啟用所有牌組 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用選擇的牌組(眾)  - + Disable selected set(s) 停用選擇的牌組(眾) - + Deck Editor 牌庫編輯 - + Use CTRL+A to select all sets in the view. 請按 CTRL + A, 以此選擇所有看到的牌組 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已經被啟用的卡牌組中的卡牌,才會在牌庫編輯中的卡牌清單出現 - + Image priority is decided in the following order: 卡牌圖片先後次序由以下次序決定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自訂檔案夾(%`1)優先, 此對話窗中已被啟用的牌組為後(由頂到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌藝術 - + How to use custom card art 如何使用自定卡牌圖片 - + Hints 提示 - + Note 筆記 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級別的情況下查找牌組。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 用現在排序代替牌組先後 - + Sorts the set priority using the same column 用同一列分牌組先後 - + Manage sets 管理牌組 @@ -10093,72 +10349,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分組 - + Group by Type 按類型分組 - + Group by Mana Value 按法力數值分組 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名稱排次序 - + Sort by Type 按類型排序 - + Sort by Mana Cost 按法力費用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防禦力排序 - + Sort by Set 按排組排序 - + shuffle when closing 關閉界面時洗牌 - + pile view 棟疊觀看 @@ -10193,7 +10449,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor 牌庫編輯 @@ -10274,7 +10530,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays 遊戲錄像 @@ -10426,7 +10682,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout 重置佈局 @@ -10847,8 +11103,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - 鎖定重置狀態 + Toggle Skip Untapping + Toggle Untap + @@ -10867,98 +11124,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + + + + Attach Card... 结附牌 - + Unattach Card 解除牌结附 - + Clone Card 複製卡牌 - + Create Token... 派出令牌 - + Create All Related Tokens 派出所有相關令牌 - + Create Another Token 再做另外令牌 - + Set Annotation... 記下註解 - + Select All Cards in Zone 選擇所有在區域的牌 - + Select All Cards in Row 選擇所有橫行牌 - + Select All Cards in Column 選擇所有直行牌 - + Reveal Selected Cards to All Players - - + + Bottom of Library 牌庫底 - + - - + + Exile 放逐 - + - + Graveyard 墳場 - + Hand 手牌 - - + + Top of Library 牌庫頂 - - + Battlefield, Face Down 戰場,面朝下 @@ -10994,234 +11255,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack 疊區 - + Graveyard (Multiple) 多張送墳場 - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) 多張放逐 - + + + Exile (Multiple), Face Down + + + + Stack Until Found 找到以下牌前全送疊區 - + Draw Bottom Card 從牌庫底抽一張牌 - + Draw Multiple Cards from Bottom... 從牌庫底抽超過一張牌 - + Draw Arrow... 畫箭咀 - + Remove Local Arrows 清除自設箭咀 - + Leave Game 離開遊戲 - + Concede 投降 - + Roll Dice... 擲骰 - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再調度 - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card 抽一張牌 - + Draw Multiple Cards... 抽超過一張牌 - + Undo Draw 撤銷抽牌 - + Always Reveal Top Card 永遠顯示頂牌 - + Always Look At Top Card 永遠看到頂牌 - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise 視角順時針轉 - + Rotate View Counterclockwise 視角逆時針轉 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天 - + Clear Chat 掃清聊天 - + Refresh 刷新 - + Skip Forward 跳前 - + Skip Backward 跳後 - + Skip Forward by a lot 向前大跳 - + Skip Backward by a lot 向後大跳 - + Play/Pause 播放/暫停 - + Toggle Fast Forward 切換快進 - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index f26c5939b..3e219b572 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + - + Error 出现错误 - + Could not create themes directory at '%1'. 不能在'%1'创造主题目录 - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings 主题设定 - + Current theme: 现在主题 - + Open themes folder 打开主题文件夹 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings 选单设定 - + Show keyboard shortcuts in right-click menus 在右击选单中显示键盘快速键 - + Show game filter toolbar above list in room tab - + Card rendering 卡面算绘 - + Display card names on cards having a picture 最有图片的牌上显示牌名 - + Auto-Rotate cards with sideways layout 将打横佈局卡片自动向上旋转 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector 将牌库中包含的牌组推到艺术选择视窗之顶 - + Scale cards on mouse over 卡牌随游标缩放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand 在迭区中和直行手牌中,尽量减少交重迭百分点 - + Maximum initial height for card view window: 卡望框最高开头高度 - - + + rows 横列 - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打栋显示手牌(浪费空间) - + Enable left justification 啟用向左对齐 - + Table grid layout 表格布局 - + Invert vertical coordinate 反转垂直坐标 - + Minimum player count for multi-column layout: 界面佈局之内能够容纳的最少玩家数量: - + Maximum font size for information displayed on cards: 卡牌上显示的最大字体大小 @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -332,12 +315,12 @@ Are you sure you would like to disable this feature? Theme - + 卡牌主题 Art crop of random card - + 随机卡牌:艺术取料 @@ -642,22 +625,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 查看关联卡牌 - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新预览卡牌 - - + + Success 成功 - + Download URLs have been reset. 下载URL已重置。 - + Downloaded card pictures have been reset. 下载的卡牌图片已被重置。 - + Error 出现错误 - + One or more downloaded card pictures could not be cleared. 一幅或以上的卡牌图片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Network Cache Size: 图片网路缓存大小: - + Redirect Cache TTL: 网路缓处再定向生存时间 - + How long cached redirects for urls are valid for. 再定向缓处有效时间 - + Picture Cache Size: 图片缓存大小: - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Day(s) - + Updating... 更新中... - + Choose path 选择路径 - + URL Download Priority URL下载优先次序 - + Spoilers 预览 - + Download Spoilers Automatically 自动下载预览 - + Spoiler Location: 预览位置: - + Last Change 最后变更 - + Spoilers download automatically on launch 开啟时自动下载预览 - + Press the button to manually update without relaunching 免重新啟动人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能关掉设置 - + Download card pictures on the fly 即时下载卡牌图片 - + How to add a custom URL 如何添加自定URL - + Delete Downloaded Images 删除已下载的图片 - + Reset Download URLs 重置下载URL - + On-disk cache for downloaded pictures 下载图片专用硬盘缓储 - + In-memory cache for pictures not currently on screen 现不在萤光幕上记忆中图片缓储 @@ -1484,32 +1467,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count 数目 - + Set 牌组 - + Number 号码 - + Provider ID 提供者 id - + Card 卡牌 @@ -1545,12 +1528,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ This is only saved for moderators and cannot be seen by the banned person.解锁锁上 - - + + Error 出现错误 - + The selected file could not be loaded. 选择的档案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 从剪贴板载入牌库 - + Error 出现错误 - + Invalid deck list. 牌表不能载入 @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ 载入牌库 + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名称(或搜寻表达式) - + Number of hits: 因为cockatrice原生没有ssl支援, 所以你不能自动下载更新. 请瀏览到我们的下载网页,进行人手更新. - + Auto play hits 开关副牌上锁 - + Put top cards on stack until... 把顶牌放在重迭区, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌资料库中找不到符合搜寻表达式的卡牌.你想继续吗? - + Cockatrice Cockatrice - + Invalid filter 筛选无效 @@ -3153,12 +3169,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 读取卡牌资料库时出现不知名错误 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3175,7 +3191,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database version is too old. This can cause problems loading card information or images @@ -3192,7 +3208,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3205,7 +3221,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + File Error loading your card database. Would you like to change your database location setting? @@ -3213,7 +3229,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3222,7 +3238,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3238,59 +3254,59 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的资料库位置设定? - - - + + + Error 出现错误 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌库目录路径现在无效. 你想要重新设置套牌路径吗? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌图片目录路径现在无效. 你想要重新设置卡牌图片资料库路径吗? - + Settings 设定 - + General 一般 - + Appearance 外观 - + User Interface 用户界面 - + Card Sources 卡牌来源 - + Chat 聊天 - + Sound 声音 - + Shortcuts 快捷键 @@ -3604,67 +3620,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4112,143 +4128,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路径 - + All paths have been reset 重置所有路径 - - - - - - - + + + + + + + Choose path 选择路径 - + Personal settings 个人设定 - + Language: 语言: - + Paths (editing disabled in portable mode) 路径(在便携模式下不能编辑) - + Paths 路径 - + How to help with translations - + Decks directory: 牌库路径: - + Filters directory: - + Replays directory: 游戏录像目录: - + Pictures directory: 图片目录: - + Card database: 卡牌资料库: - + Custom database directory: 自主资料库目录 - + Token database: 令牌资料库: - + Update channel 更新渠道 - + Check for client updates on startup 啟动时检查客户更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客户端缺少伺服器支持的更功能时,请提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟动新版本Cockatrice时,自动运行Oracle. - + Show tips on startup 显示启动时提示 - + Last update check on %1 (%2 days ago) @@ -4256,47 +4272,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4304,88 +4320,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4393,52 +4409,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4446,193 +4462,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4726,18 +4762,8 @@ Will now login. 现在登入. - - Number of players - 玩家人数 - - - - Please enter the number of players. - 请输入玩家人数 - - - - + + Player %1 玩家 %1 @@ -4840,8 +4866,8 @@ Will now login. - - + + Error 出现错误 @@ -5247,36 +5273,36 @@ Local version is %1, remote version is %2. 显示/隐藏 - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你将Cockatrice升级为%1. 现在会帮你啟动oracle, 更新你的卡牌资料库. - + Cockatrice installed Cockatrice 已被安装 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安装Cockatrice%1. 现在会帮你啟动oracle, 安装你的卡牌资料库. - + Card database 卡牌资料库 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5285,29 +5311,29 @@ If unsure or first time user, choose "Yes" 如果不确定, 或者是第一次使用,请选择“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打开设置 - + New sets found 发现新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5316,17 +5342,17 @@ Do you want to enable it/them? 要啟用它们吗? - + View sets 查看卡组 - + Welcome 欢迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5335,64 +5361,64 @@ Read more about changing the set order or disabling specific sets and consequent 请阅读,了解更多在“卡组设置”对话框裡.更改卡组顺序或者禁用某些卡组的功能. - - + + Information 资讯 - + A card database update is already running. 资料库更新现已进行. - + Unable to run the card database updater: 不能运行卡牌资料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5404,54 +5430,54 @@ To update your client, go to Help -> Check for Updates. 请到"援助->检查更新"去更新你的客户端. - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到选择的文件。 - + You can only import XML databases at this time. 你暂时只能导入xml资料库. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已经成功添加新的卡组/卡牌. Cockatrice会重新载入卡牌资料库. - + Sets/cards failed to import. 卡组/卡牌导入失败. - - - + + + Reset Password 重设密码 - + Your password has been reset successfully, you can now log in using the new credentials. 你的密码已经成功重置, 你可以用新密码重新登入。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置用户密码失败, 请联络伺服器管责人重置密码. - + Activation request received, please check your email for an activation token. 已经收到啟动要求, 请从你的电邮获取啟动验证码. @@ -5502,7 +5528,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5656,590 +5682,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 从战场上 - + from their graveyard 从它们的坟场 - + from exile 从放逐区 - + from their hand 从它们的手牌 - + the top card of %1's library %1牌库的顶牌 - + the top card of their library 它们的顶牌 - + from the top of %1's library 从%1牌库的顶 - + from the top of their library 从它们1牌库顶 - + the bottom card of %1's library %1牌库的底牌 - + the bottom card of their library 它们牌库的底牌 - + from the bottom of %1's library 从%1的牌库底 - + from the bottom of their library 从它们的牌库底 - + from %1's library 从%1的牌库 - + from their library 从它们的牌库 - + from sideboard 从备牌中 - + from the stack 从迭区中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1现在保持%2顶牌显现. - + %1 is not revealing the top card %2 any longer. %1不再展示牌库顶牌%2。 - + %1 can now look at top card %2 at any time. %1可以随时查看牌库顶牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以随时查看牌库顶牌 %2. - + %1 attaches %2 to %3's %4. %1 结附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此游戏宣佈降服. - + %1 has unconceded the game. %1 收回在此游戏降服. - + %1 has restored connection to the game. %1已恢復连接。 - + %1 has lost connection to the game. %1 已失去连接。 - + %1 points from their %2 to themselves. %1 从它们的%2 指向自己。 - + %1 points from their %2 to %3. %1 从它们的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 从%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 从%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 从它们的%2指向它们的%3。 - + %1 points from their %2 to %3's %4. %1 从它们的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 从%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 从%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌库(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已载入含有 %2张备牌的牌库(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一张牌 - + %1 gives %2 control over %3. %1将%3的控制转移给%2. - + %1 puts %2 into play%3 face down. %1将%2%3牌面朝下地放进战场。 - + %1 puts %2 into play%3. %1将%2放进战场%3。 - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1将%2%3置入它们的的坟场。 + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1将%2%3移到它们的的手牌中。 - + %1 puts %2%3 into their library. %1将%2%3放入它们的牌库。 - + %1 puts %2%3 onto the bottom of their library. %1将%2%3放在它们的牌库底部。 - + %1 puts %2%3 on top of their library. %1将%2%3放在它们的牌库顶部。 - + %1 puts %2%3 into their library %4 cards from the top. %1将%2%3放在它们的牌库顶部%4张牌以下。 - + %1 moves %2%3 to sideboard. %1 将%2%3移到副牌库. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1使出 %2%3。 - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1尝试从空牌库抽牌 - + %1 draws %2 card(s). %1抽%2张牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3张卡牌. - + bottom 底部 - + top 顶部 - + %1 turns %2 face-down. %1把%2翻为面朝下。 - + %1 turns %2 face-up. %1把%2翻为面朝上。 - + The game has been closed. 游戏已经关闭。 - + The game has started. 游戏已开始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已经加入游戏。 - + %1 is now watching the game. %1 正在旁观游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 - + %1 has left the game (%2). %1 已经离开游戏(%2)。 - + %1 is not watching the game any more (%2). %1 已经不再旁观游戏(%2)。 - + %1 is not ready to start the game any more. %1 还未準备好开始游戏。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1将其牌库洗牌,然后抽%2张牌作为新的起手。 - + %1 shuffles their deck and draws a new hand. %1将其牌库洗牌,然后抽起新的手牌。 - + You are watching a replay of game #%1. 你正在观看游戏#%1的录像。 - + %1 is ready to start the game. %1已準备好开始游戏。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借给%3. - + %1 reveals %2 to %3. %1把%2展示给%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1随机展示%2%3给%4。 - + %1 randomly reveals %2%3. %1随机展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3给%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆转回合次序,现在轮到%2。 - + reversed 逆转 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1掷了硬币。结果为%2。 - + %1 rolls a %2 with a %3-sided die. %1掷%3面骰子,结果为%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1掷了%2个硬币。结果为%3个正面和%4个反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子掷了%3次:%4 - + %1's turn. 轮到 %1. - + %1 sets annotation of %2 to %3. %1给%2添加了註释成%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 将%2指示物设置为 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 将 %2 设置为不被重置。 - + %1 sets %2 to untap normally. %1 将 %2 设置为照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防御. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防御从无数值转为%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防御从%3转为%4. - + %1 has locked their sideboard. %1已经锁定它们的的副牌库。 - + %1 has unlocked their sideboard. %1已经把它们的的副牌库解锁。 - + %1 taps their permanents. %1横置它们的永久卡牌 - + %1 untaps their permanents. %1重置它们的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 对 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 张卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 顶的 %3 张卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的结附。 - + %1 undoes their last draw. %1 放回它们最后一张抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它们最后一张抽到的卡牌(%2). @@ -6247,110 +6293,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 词1 词2 词3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天设定 - + Custom alert words 自定通知触发词语 - + Enable chat mentions 允许聊天中提到某人 - + Enable mention completer 允许自动完成提名 - + In-game message macros 游戏内短信速键 - + How to use in-game message macros 如何使用游戏内短信速键 - + Ignore chat room messages sent by unregistered users 不要显示未註册用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要显示未註册用户的私人消息. - - + + Invert text color 反转文本顏色 - + Enable desktop notifications for private messages 容许桌面提醒提及私人消息 - + Enable desktop notification for mentions 容许桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室时开启消息历史 - - + + (Color is hexadecimal) (顏色为16进制) - + Separate words with a space, alphanumeric characters only 将单词以空格区分,仅支持字母. @@ -6363,32 +6409,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6530,57 +6581,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step 重置步 - + Upkeep step 维持步 - + Draw step 抓牌步 - + First main phase 第一主阶段 - + Beginning of combat step 战斗开始步 - + Declare attackers step 宣告阻挡步 - + Declare blockers step 宣告阻挡步 - + Combat damage step 战斗损害步 - + End of combat step 战斗结束步 - + Second main phase 第二主阶段 - + End of turn step 回合完结部 @@ -6597,134 +6648,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6732,48 +6787,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上别针 - + Unpin Printing 版本脱别针 - + Show Related cards 显示关联卡牌 @@ -6822,17 +6894,25 @@ Cockatrice will now reload the card database. 发行日期 - - + + Descending 由后到先 - + Ascending 由先倒后 + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6971,6 +7051,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7119,37 +7226,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7192,6 +7299,14 @@ Cockatrice will now reload the card database. 游戏(眾) + + SayMenu + + + S&ay + + + SequenceEdit @@ -7256,53 +7371,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快键 - + Do you really want to restore all default shortcuts? 确定要恢復所有常用快键吗? - + Clear all default shortcuts 清除所有常用快键 - + Do you really want to clear all shortcuts? 确认清除所有快键吗? - + Section: 部分: - + Action: 行动: - + Shortcut: 快键: - + How to set custom shortcuts 如何设置自定快键 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快键名称搜寻 @@ -7371,27 +7486,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 启用&声效 - + Current sounds theme: 现前声效主题: - + Test system sound engine 测试系统声效引擎 - + Sound settings 声效设置 - + Master volume 主音量 @@ -7607,59 +7722,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7667,60 +7846,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌资讯 - + Deck 牌库 - + Filters 筛选项目 - + &View &视图 - + Card Database - + Printing 印刷版本 - - - - - + Visible 可见 - - - - - + Floating 浮动 - + Reset layout 重置布局 - + Deck: %1 牌库:%1 @@ -7728,61 +7899,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7847,7 +8012,7 @@ Please check your shortcut settings! - + New folder 新文件夹 @@ -7928,18 +8093,18 @@ Please enter a name: 你确定要删除被选档案(眾)吗 ? - + Delete remote decks 删除伺服器上的牌库 - + Are you sure you want to delete the selected decks? 你确定要剷除所有被选的牌库吗? - + Name of new folder: 新建文件夹名称: @@ -7957,12 +8122,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -8011,197 +8176,191 @@ Please enter a name: TabGame - - - + + + Replay 游戏录像重温 - - + + Game 游戏 - - + + Player List 玩家列表 - - + + Card Info 卡牌资讯 - - + + Messages 信息 - - + + Replay Timeline 录像时间线 - + &Phases 阶段 - + &Game 游戏 - + Next &phase 下个&阶段 - + Next phase with &action 下个阶段&行动 - + Next &turn 下个&回合 - + Reverse turn order 逆转回合次序 - + &Remove all local arrows &移除所有自设箭头 - + Rotate View Cl&ockwise &顺时针旋转视角 - + Rotate View Co&unterclockwise &逆时针旋转视角 - + Game &information 游戏&资讯 - + Un&concede 撤销&投降 - - - + + + &Concede &投降 - + &Leave game &离开游戏 - + C&lose replay &关闭游戏录像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &视图 - - - - + Visible 可见 - - - - + Floating 浮动 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你确定要宣佈投降吗? - + Unconcede 撤销投降 - + You have already conceded. Do you want to return to this game? 你已经宣佈投降,你想回到这场游戏吗? - + Leave game 离开游戏 - + Are you sure you want to leave this game? 你确定要离开这个游戏吗? - + A player has joined game #%1 有玩家加入游戏 #%1 - + %1 has joined the game %1 已经加入游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 @@ -9301,142 +9460,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口设置 - + &Double-click cards to play them (instead of single-click) 双点击使出卡牌 (而不是单点击使出) - + &Clicking plays all selected cards (instead of just the clicked card) &单一点击把所有选择的卡牌使出(不只是被点击的单张卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆迭区(不是战场) 作为常用设置 - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 在令牌上标註牌中文字 - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 - - - - Notifications settings - 通告设置 - - - - Enable notifications in taskbar - 开启任务栏通告 - - - - Notify in the taskbar for game events while you are spectating - 观看时在任务栏提示游戏信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任务栏中提示好友连到伺服器 - - - - Animation settings - 动画设定 - - - - &Tap/untap animation - &横置/重置 动画 - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - 把用新页开啟牌库作为常用设置 - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通告设置 + + + + Enable notifications in taskbar + 开启任务栏通告 - do nothing - + Notify in the taskbar for game events while you are spectating + 观看时在任务栏提示游戏信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任务栏中提示好友连到伺服器 - ask to convert to .cod - + Animation settings + 动画设定 + + + + &Tap/untap animation + &横置/重置 动画 - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + 把用新页开啟牌库作为常用设置 - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings 重播设置 - + Buffer time for backwards skip via shortcut: 用速键啟动向后跳的缓衝时间: @@ -9500,23 +9669,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9543,25 +9713,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9569,22 +9822,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9620,7 +9883,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9628,19 +9891,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9648,27 +9911,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9682,52 +9955,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9735,56 +9978,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9792,17 +10043,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9818,47 +10069,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9959,133 +10215,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 将选择的牌组送到最顶 - + Move selected set up 将选择的牌组往上一格 - + Move selected set down 将选择的牌组往下一格 - + Move selected set to the bottom 将选择的牌组送到最底 - + Search by set name, code, or type 用排组名称,代码,或类型进行搜寻 - + Default order 常用次序 - + Restore original art priority order 恢復常用艺术先后次序 - + Enable all sets 啟用所有牌组 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用选择的牌组(眾)  - + Disable selected set(s) 停用选择的牌组(眾) - + Deck Editor 牌库编辑 - + Use CTRL+A to select all sets in the view. 请按 CTRL + A, 以此选择所有看到的牌组 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已经被啟用的卡牌组中的卡牌,才会在牌库编辑中的卡牌清单出现 - + Image priority is decided in the following order: 卡牌图片先后次序由以下次序决定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自订档案夹(%`1)优先, 此对话窗中已被啟用的牌组为后(由顶到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌艺术 - + How to use custom card art 如何使用自定卡牌图片 - + Hints 提示 - + Note 笔记 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允许您在不更改系列优先级别的情况下查找牌组。 - + To enable ordering again, click the column header until this message disappears. 要再次启用排序,请单击列标题,直到此消息消失。 - + Use the current sorting as the set priority instead 用现在排序代替牌组先后 - + Sorts the set priority using the same column 用同一列分牌组先后 - + Manage sets 管理牌组 @@ -10093,72 +10349,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分组 - + Group by Type 按类型分组 - + Group by Mana Value 按法力数值分组 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名称排次序 - + Sort by Type 按类型排序 - + Sort by Mana Cost 按法力费用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防御力排序 - + Sort by Set 按排组排序 - + shuffle when closing 关闭界面时洗牌 - + pile view 栋迭观看 @@ -10193,7 +10449,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor 牌库编辑 @@ -10274,7 +10530,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays 游戏录像 @@ -10426,7 +10682,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout 重置佈局 @@ -10847,8 +11103,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - 锁定重置状态 + Toggle Skip Untapping + Toggle Untap + @@ -10867,98 +11124,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + + + + Attach Card... 结附牌 - + Unattach Card 解除牌结附 - + Clone Card 复製卡牌 - + Create Token... 派出令牌 - + Create All Related Tokens 派出所有相关令牌 - + Create Another Token 再做另外令牌 - + Set Annotation... 记下註解 - + Select All Cards in Zone 选择所有在区域的牌 - + Select All Cards in Row 选择所有横行牌 - + Select All Cards in Column 选择所有直行牌 - + Reveal Selected Cards to All Players - - + + Bottom of Library 牌库底 - + - - + + Exile 放逐 - + - + Graveyard 坟场 - + Hand 手牌 - - + + Top of Library 牌库顶 - - + Battlefield, Face Down 战场,面朝下 @@ -10994,234 +11255,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack 迭区 - + Graveyard (Multiple) 多张送坟场 - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) 多张放逐 - + + + Exile (Multiple), Face Down + + + + Stack Until Found 找到以下牌前全送迭区 - + Draw Bottom Card 从牌库底抽一张牌 - + Draw Multiple Cards from Bottom... 从牌库底抽超过一张牌 - + Draw Arrow... 画箭咀 - + Remove Local Arrows 清除自设箭咀 - + Leave Game 离开游戏 - + Concede 投降 - + Roll Dice... 掷骰 - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再调度 - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card 抽一张牌 - + Draw Multiple Cards... 抽超过一张牌 - + Undo Draw 撤销抽牌 - + Always Reveal Top Card 永远显示顶牌 - + Always Look At Top Card 永远看到顶牌 - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise 视角顺时针转 - + Rotate View Counterclockwise 视角逆时针转 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天 - + Clear Chat 扫清聊天 - + Refresh 刷新 - + Skip Forward 跳前 - + Skip Backward 跳后 - + Skip Forward by a lot 向前大跳 - + Skip Backward by a lot 向后大跳 - + Play/Pause 播放/暂停 - + Toggle Fast Forward 切换快进 - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_zh-Hant.ts b/cockatrice/translations/cockatrice_zh-Hant.ts index ce3fa8f00..1980960a9 100644 --- a/cockatrice/translations/cockatrice_zh-Hant.ts +++ b/cockatrice/translations/cockatrice_zh-Hant.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,60 +36,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -131,190 +131,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 錯誤 - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings 主題設定 - + Current theme: 當前主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering 牌面 - + Display card names on cards having a picture 顯示有圖卡牌的名稱 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 卡牌隨指針縮放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌區域佈局 - + Display hand horizontally (wastes space) 水平顯示手牌區域 (浪費空間) - + Enable left justification 開啓左對齊 - + Table grid layout 表格佈局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的玩家欄數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字號 @@ -322,7 +300,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -642,22 +625,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -693,124 +676,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -826,133 +809,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1014,7 +997,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1022,7 +1005,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1045,32 +1028,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1078,32 +1061,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1176,17 +1159,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1309,7 +1292,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1317,166 +1300,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽 - - + + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Downloaded card pictures have been reset. 下載的卡牌圖片已被重置。 - + Error 錯誤 - + One or more downloaded card pictures could not be cleared. 1個或多個卡牌圖片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 編輯URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL 添加新的URL - + Remove URL 移除URL - + Day(s) - + Updating... 更新中... - + Choose path 選擇路徑 - + URL Download Priority URL下載優先級 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最近的變更 - + Spoilers download automatically on launch 自動下載預覽運行 - + Press the button to manually update without relaunching 按下按鈕手動更新而不重新啓動 - + Do not close settings until manual update is complete 手動更新完成之前,請勿關閉設置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定義URL - + Delete Downloaded Images 刪除已下載的圖片 - + Reset Download URLs 重置用於下載的URL - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1484,32 +1467,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1517,27 +1500,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count - + Set - + Number 數量 - + Provider ID - + Card 卡牌 @@ -1545,12 +1528,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1647,94 +1630,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1767,32 +1750,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1859,29 +1842,29 @@ This is only saved for moderators and cannot be seen by the banned person.鎖定備牌 - - + + Error 錯誤 - + The selected file could not be loaded. 此文件無法被載入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2885,17 +2868,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 從剪貼板讀取套牌 - + Error 錯誤 - + Invalid deck list. 無效的牌表. @@ -2903,43 +2886,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2958,40 +2941,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ 讀取套牌 + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3152,12 +3168,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 讀取卡牌數據庫時出現未知錯誤 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3174,7 +3190,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database version is too old. This can cause problems loading card information or images @@ -3191,7 +3207,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3200,7 +3216,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3209,7 +3225,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌數據庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3218,7 +3234,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3227,59 +3243,59 @@ Would you like to change your database location setting? - - - + + + Error 錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的套牌路徑無效。你想要重新設置正確的路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你想要重新設置卡牌數據庫路徑嗎? - + Settings 設定 - + General 常規 - + Appearance 外觀 - + User Interface 用戶界面 - + Card Sources 卡牌的來源 - + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷鍵 @@ -3593,67 +3609,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4101,143 +4117,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path 選擇路徑 - + Personal settings 個人設定 - + Language: 語言: - + Paths (editing disabled in portable mode) 路徑(在便攜模式下禁止編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 套牌路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌數據庫: - + Custom database directory: - + Token database: 衍生物數據庫: - + Update channel 更新通道 - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 當客戶端缺少伺服器支持的特性時提示我 - + Automatically run Oracle when running a new version of Cockatrice 運行新版本雞蛇時自動運行Oracle - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -4245,47 +4261,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4293,88 +4309,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4382,52 +4398,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4435,193 +4451,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4715,18 +4751,8 @@ Will now login. 現在登陸 - - Number of players - 玩家人數 - - - - Please enter the number of players. - 請輸入玩家的數量. - - - - + + Player %1 玩家 %1 @@ -4829,8 +4855,8 @@ Will now login. - - + + Error 錯誤 @@ -5237,36 +5263,36 @@ Local version is %1, remote version is %2. - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + Cockatrice installed 雞蛇已安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + Card database 卡牌數據庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5275,46 +5301,46 @@ If unsure or first time user, choose "Yes" 如果不確定或者你是第一次使用,請選擇“是” - - + + Yes - - + + No - + Open settings 打開設定 - + New sets found 發現新系列 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets 查看系列 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5323,64 +5349,64 @@ Read more about changing the set order or disabling specific sets and consequent 瞭解更多用“系列管理”窗口更改系列順序或者禁用某系列的信息。 - - + + Information 信息 - + A card database update is already running. 數據庫更新已經在運行中。 - + Unable to run the card database updater: 無法運行卡組數據庫更新器: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5390,55 +5416,55 @@ To update your client, go to Help -> Check for Updates. 要升級客户端,請點擊 幫助->檢查更新 - - - - - + + + + + Load sets/cards 載入系列/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 當前只能導入XML格式數據庫。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新的系列/卡牌已添加成功。 CockatriceCockatrice現在會重新載入卡組數據庫。 - + Sets/cards failed to import. 系列/卡牌導入失敗。 - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. 你的密碼已重置,現在你可以使用新密碼登錄了。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置密碼失敗,請聯繫伺服器管理員重置密碼。 - + Activation request received, please check your email for an activation token. 激活請求已收到,請查看你的電子郵箱獲取激活驗證碼。 @@ -5489,7 +5515,7 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 ManaBaseWidget - + Mana Base @@ -5643,590 +5669,610 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MessageLogWidget - + from play 從戰場上 - + from their graveyard 從他的墳墓場 - + from exile 從放逐區 - + from their hand 從他的手牌 - + the top card of %1's library %1牌庫頂的牌 - + the top card of their library 他牌庫頂的卡牌 - + from the top of %1's library 從%1的牌庫頂 - + from the top of their library 從他的牌庫頂 - + the bottom card of %1's library %1的牌庫底牌 - + the bottom card of their library 他牌庫底部的牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從他的牌庫底 - + from %1's library 從%1的牌庫中 - + from their library 從他的牌庫 - + from sideboard 從備牌中 - + from the stack 從堆疊中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1已持續展示牌庫頂牌%2 - + %1 is not revealing the top card %2 any longer. %1不再%2展示牌庫頂牌。 - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 已放棄遊戲。 - + %1 has unconceded the game. %1 已放棄遊戲。 - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 將%2 指向自己。 - + %1 points from their %2 to %3. %1 將%2指向%3。 - + %1 points from %2's %3 to themselves. %1將%2的%3 - + %1 points from %2's %3 to %4. %1 將%2的%3指向%4。 - + %1 points from their %2 to their %3. %1將他的%2指向他的%3。 - + %1 points from their %2 to %3's %4. %1將他的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1將%2的%3指向他自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 將%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1創建衍生物:%2%3。 - + %1 has loaded a deck (%2). %1 已載入套牌(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的套牌(%3) - + %1 destroys %2. %1銷燬了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1讓%2控制%3。 - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1將%2%3放進戰場。 - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. %1將%2%3置入他的墳墓場。 + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. %1 %3放逐了 %2 - + %1 moves %2%3 to their hand. %1將%2%3放回他的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入他的牌庫。 - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1將%2%3置於其牌庫頂。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放入牌庫%4張牌放在牌庫頂 - + %1 moves %2%3 to sideboard. %1 將%2%3移動到備牌 - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. %1使用 %2%3。 - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1回合%2翻為面朝下。 - + %1 turns %2 face-up. %1回合%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開了遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1洗牌並抓了新的起手 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1將%2展示給%3。 - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 反轉回合順序,現在是%2的回合。 - + reversed 反轉 - + normal 正常 - + Heads 正面 - + Tails 反面 - + %1 flipped a coin. It landed as %2. %1擲硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. 現在是%1的回合。 - + %1 sets annotation of %2 to %3. %1給%2添加了註釋%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不會被通常重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為可通常重置。 - + %1 removes the PT of %2. %1 移除%2力量/防禦 - + %1 changes the PT of %2 from nothing to %4. %1改變%2的力量/防禦至%4 - + %1 changes the PT of %2 from %3 to %4. %1將%2的力量/防禦從%3改變至%4 - + %1 has locked their sideboard. %1已鎖定他的備牌。 - + %1 has unlocked their sideboard. %1 解除鎖定他的備牌。 - + %1 taps their permanents. %1 橫置了永久物。 - + %1 untaps their permanents. %1 重置了永久物。 - + %1 taps %2. %1 橫置了 %2。 - + %1 untaps %2. %1 重置了 %2。 - + %1 shuffles %2. %1 切洗了%2。 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡 - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡 - + %1 shuffles cards %3 - %4 of %2. %1洗%3 - %4 到%2. - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1撤銷了最後的抓牌。 - + %1 undoes their last draw (%2). %1撤銷了最後的抓牌(%2)。 @@ -6234,110 +6280,110 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 編輯信息 - + Remove Message - + Add message 添加信息 - - + + Message: 信息: - + Edit message 編輯消息 - + Chat settings 聊天設定 - + Custom alert words 自定義警告語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內消息宏 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 忽略未註冊用戶的聊天室消息。 - + Ignore private messages sent by unregistered users 忽略未註冊用戶發出的私人消息 - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 開啓私人消息桌面提醒 - + Enable desktop notification for mentions 開啓聊天提名桌面提醒 - + Enable room message history on join 開啓加入聊天室時的歷史消息 - - + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母 @@ -6350,32 +6396,37 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6517,57 +6568,57 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PhasesToolbar - + Untap step 重置步驟 - + Upkeep step 維持步驟 - + Draw step 抓牌步驟 - + First main phase 主要步驟1 - + Beginning of combat step 戰鬥開始步驟 - + Declare attackers step 宣告進攻步驟 - + Declare blockers step 宣告阻擋步驟 - + Combat damage step 戰鬥傷害步驟 - + End of combat step 戰鬥結束步驟 - + Second main phase 主要步驟2 - + End of turn step 結束步驟 @@ -6584,134 +6635,138 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6719,48 +6774,65 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6809,17 +6881,25 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6958,6 +7038,33 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7106,37 +7213,37 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7179,6 +7286,14 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 遊戲 + + SayMenu + + + S&ay + + + SequenceEdit @@ -7243,53 +7358,53 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有默認快捷鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有默認快捷鍵嗎? - + Clear all default shortcuts 清楚所有默認快捷鍵 - + Do you really want to clear all shortcuts? 確定要清楚所有快捷鍵? - + Section: 部分: - + Action: 行動: - + Shortcut: 快捷鍵: - + How to set custom shortcuts 如何設置自定義快捷鍵 - + Clear all shortcuts - + Search by shortcut name @@ -7358,27 +7473,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用聲效 - + Current sounds theme: 當前聲效主題: - + Test system sound engine 測試系統聲效 - + Sound settings 聲效設定 - + Master volume 主音量 @@ -7594,59 +7709,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7654,60 +7833,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌信息 - + Deck 套牌 - + Filters 過濾器 - + &View 視圖 - + Card Database - + Printing - - - - - + Visible 可見 - - - - - + Floating 移動窗口 - + Reset layout 重置界面 - + Deck: %1 套牌: %1 @@ -7715,61 +7886,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7834,7 +7999,7 @@ Please check your shortcut settings! - + New folder 新建文件夾 @@ -7916,18 +8081,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: 新建文件夾的名稱: @@ -7945,12 +8110,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7999,197 +8164,191 @@ Please enter a name: TabGame - - - + + + Replay 錄像 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + Card Info 卡牌信息 - - + + Messages 消息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個回合 - + Reverse turn order 反轉回合順序 - + &Remove all local arrows 移除所有箭頭 - + Rotate View Cl&ockwise 順時針旋轉視角 - + Rotate View Co&unterclockwise 逆時針旋轉視角 - + Game &information 遊戲信息 - + Un&concede - - - + + + &Concede 放棄遊戲 - + &Leave game 離開遊戲 - + C&lose replay 關閉遊戲錄像 - + &Focus Chat 聚焦聊天室 - + &Say: 説: - + Selected cards - + &View 視圖 - - - - + Visible 可見 - - - - + Floating 移動窗口 - + Reset layout 重置界面 - + Concede 投降 - + Are you sure you want to concede this game? 你確定放棄這個遊戲? - + Unconcede 取消投降 - + You have already conceded. Do you want to return to this game? 你已經投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定離開這這個遊戲? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -9291,142 +9450,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 通用接口設定 - + &Double-click cards to play them (instead of single-click) 雙擊卡牌開始 (而不是單擊開始) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 默認將所有非地牌加入堆疊(不是戰場) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 用卡牌信息給衍生物標注 - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮動菜單,允許右鍵單擊菜單保留在屏幕上 - - - - Notifications settings - 通知設定 - - - - Enable notifications in taskbar - 開啓任務欄提醒 - - - - Notify in the taskbar for game events while you are spectating - 觀看時在任務欄提示遊戲信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任務欄中提示好友連接到伺服器 - - - - Animation settings - 動畫設定 - - - - &Tap/untap animation - 橫置/重置 動畫 - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮動菜單,允許右鍵單擊菜單保留在屏幕上 + - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通知設定 + + + + Enable notifications in taskbar + 開啓任務欄提醒 - do nothing - + Notify in the taskbar for game events while you are spectating + 觀看時在任務欄提示遊戲信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任務欄中提示好友連接到伺服器 - ask to convert to .cod - + Animation settings + 動畫設定 + + + + &Tap/untap animation + 橫置/重置 動畫 - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9490,23 +9659,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9533,25 +9703,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9559,22 +9812,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9610,7 +9873,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9618,19 +9881,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9638,27 +9901,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9672,52 +9945,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9725,56 +9968,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9782,17 +10033,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9808,47 +10059,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9949,133 +10205,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 將選擇的系列移到頂端 - + Move selected set up 將選擇的系列上移 - + Move selected set down 將選擇的系列下移 - + Move selected set to the bottom 將選擇的系列移到底部 - + Search by set name, code, or type 通過系列名、代碼或類型搜索 - + Default order 默認順序 - + Restore original art priority order 恢復初始優先順序 - + Enable all sets 啓用所有系列 - + Disable all sets 禁用所有系列 - + Enable selected set(s) 啓用所選系列 - + Disable selected set(s) 禁用所選系列 - + Deck Editor 套牌編輯器 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 牌張風格 - + How to use custom card art 如何使用自定義卡牌圖片 - + Hints 提示 - + Note - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級的情況下查找系列。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 使用當前系列的優先級排序 - + Sorts the set priority using the same column 使用同一列系列的優先級排序 - + Manage sets 系列管理 @@ -10083,72 +10339,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 當關閉界面時洗牌 - + pile view 柱形圖 @@ -10183,7 +10439,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor 套牌編輯 @@ -10264,7 +10520,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10416,7 +10672,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout 重置佈局 @@ -10837,8 +11093,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - 鎖定重置狀態 + Toggle Skip Untapping + Toggle Untap + @@ -10857,98 +11114,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + + + + Attach Card... 結附卡牌 - + Unattach Card 取消結附 - + Clone Card 複製卡牌 - + Create Token... 派出衍生物 - + Create All Related Tokens 派出所有相關的衍生物 - + Create Another Token 派出另一個衍生物 - + Set Annotation... 設置注釋... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library 牌庫底 - + - - + + Exile 放逐區 - + - + Graveyard 墳墓場 - + Hand 手牌 - - + + Top of Library 牌庫頂 - - + Battlefield, Face Down 戰場,面朝下 @@ -10984,234 +11245,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack 堆疊 - + Graveyard (Multiple) 墳場(多個) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) 放逐區(多個) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... 畫箭頭... - + Remove Local Arrows 移除本地箭頭 - + Leave Game 離開遊戲 - + Concede 投降 - + Roll Dice... 擲骰子... - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再調度起手牌 - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card 抓一張牌 - + Draw Multiple Cards... 抓多張牌 - + Undo Draw 撤銷抓牌 - + Always Reveal Top Card 一直展示牌庫頂牌 - + Always Look At Top Card 一直檢視牌庫頂牌 - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise 順時針旋轉視角 - + Rotate View Counterclockwise 逆時針旋轉視角 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天室 - + Clear Chat 清除聊天記錄 - + Refresh 刷新 - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/format.sh b/format.sh index f8c183dfc..83dee9e28 100755 --- a/format.sh +++ b/format.sh @@ -3,7 +3,7 @@ # This script will run clang-format on all modified, non-3rd-party C++/Header files. # Optionally runs cmake-format on all modified cmake files. # Optionally runs shellcheck on all modified shell files. -# Uses clang-format cmake-format git diff find shellcheck +# Uses clang-format, cmake-format, git, diff, find and shellcheck # Never, ever, should this receive a path with a newline in it. Don't bother proofing it for that. set -o pipefail @@ -14,13 +14,7 @@ cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with # defaults include=("cockatrice/src" \ -"libcockatrice_card" \ -"libcockatrice_deck_list" \ -"libcockatrice_network" \ -"libcockatrice_protocol" \ -"libcockatrice_rng" \ -"libcockatrice_settings" \ -"libcockatrice_utility" \ +libcockatrice_* \ "oracle/src" \ "servatrice/src" \ "tests") @@ -109,11 +103,11 @@ OPTIONS: Do not check any source files for clang-format. --print-version - Print the version of clang-format being used before continuing. + Print the lint tool version being used before continuing. --shell - Use shellcheck to lint shell files. Not available in the default inline - mode. + Use shellcheck to lint shell files. + Not available in the default inline mode. -t, --test Do not edit files in place. Set exit code to 1 if changes are required. diff --git a/libcockatrice_card/libcockatrice/card/card_info_comparator.h b/libcockatrice_card/libcockatrice/card/card_info_comparator.h index b8d9c47e6..b6df8c7a3 100644 --- a/libcockatrice_card/libcockatrice/card/card_info_comparator.h +++ b/libcockatrice_card/libcockatrice/card/card_info_comparator.h @@ -1,8 +1,8 @@ /** * @file card_info_comparator.h * @ingroup Cards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_INFO_COMPARATOR_H #define CARD_INFO_COMPARATOR_H diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 5c4b408b3..951381aa4 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -1,6 +1,7 @@ #include "card_database.h" #include "../relation/card_relation.h" +#include "card_database_manager.h" #include "parser/cockatrice_xml_4.h" #include @@ -60,6 +61,15 @@ void CardDatabase::loadCardDatabases() loadStatus = loader->loadCardDatabases(); } +void CardDatabase::reloadCardDatabasesAndNotify() +{ + loadCardDatabases(); + + if (loadStatus == Ok) { + notifyEnabledSetsChanged(); + } +} + bool CardDatabase::saveCustomTokensToFile() { return loader->saveCustomTokensToFile(); @@ -93,9 +103,11 @@ void CardDatabase::addCard(CardInfoPtr card) // If a card already exists, just add the new set property. if (auto existing = cards.value(name)) { - for (const auto &printings : card->getSets()) - for (const auto &printing : printings) + for (const auto &printings : card->getSets()) { + for (const auto &printing : printings) { existing->addToSet(printing.getSet(), printing); + } + } return; } @@ -113,14 +125,17 @@ void CardDatabase::removeCard(CardInfoPtr card) return; } - for (auto *cardRelation : card->getRelatedCards()) + for (auto *cardRelation : card->getRelatedCards()) { cardRelation->deleteLater(); + } - for (auto *cardRelation : card->getReverseRelatedCards()) + for (auto *cardRelation : card->getReverseRelatedCards()) { cardRelation->deleteLater(); + } - for (auto *cardRelation : card->getReverseRelatedCards2Me()) + for (auto *cardRelation : card->getReverseRelatedCards2Me()) { cardRelation->deleteLater(); + } QMutexLocker locker(removeCardMutex); cards.remove(card->getName()); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index 7f8fc39db..521be8fbc 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -30,27 +30,27 @@ class CardDatabase : public QObject Q_OBJECT protected: - /// Controller to determine set priority when choosing preferred printings. + /** @brief Controller to determine set priority when choosing preferred printings. */ ICardSetPriorityController *setPriorityController; - /// Cards indexed by exact name + /** @brief Cards indexed by exact name. */ CardNameMap cards; - /// Cards indexed by simplified name (normalized) + /** @brief Cards indexed by simplified name (normalized). */ CardNameMap simpleNameCards; - /// Sets indexed by short name + /** @brief Sets indexed by short name. */ SetNameMap sets; FormatRulesNameMap formats; - /// Loader responsible for file discovery and parsing + /** @brief Loader responsible for file discovery and parsing. */ CardDatabaseLoader *loader; - /// Current load status of the database + /** @brief Current load status of the database. */ LoadStatus loadStatus; - /// Querier for higher-level card lookups + /** @brief Querier for higher-level card lookups. */ CardDatabaseQuerier *querier; private: @@ -64,7 +64,7 @@ private: */ void refreshCachedReverseRelatedCards(); - /// Mutexes for thread safety + /** @brief Mutexes for thread safety. */ QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(), *removeCardMutex = new QBasicMutex(); @@ -130,6 +130,11 @@ public: /** @brief Notifies listeners that enabled sets changed. */ void notifyEnabledSetsChanged(); + ICardSetPriorityController *getPriorityController() + { + return setPriorityController; + } + public slots: /** * @brief Adds a card to the database. @@ -147,6 +152,7 @@ public slots: /** @brief Loads card databases from configured paths. */ void loadCardDatabases(); + void reloadCardDatabasesAndNotify(); /** @brief Saves custom tokens to file. * @return True if successful. diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp index 716477a59..76abf87ce 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp @@ -125,8 +125,9 @@ QStringList CardDatabaseLoader::collectCustomDatabasePaths() const QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); QStringList paths; - while (it.hasNext()) + while (it.hasNext()) { paths << it.next(); + } paths.sort(); return paths; } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_manager.h b/libcockatrice_card/libcockatrice/card/database/card_database_manager.h index 58a744fbb..c7de66a25 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_manager.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_manager.h @@ -67,13 +67,13 @@ private: /** @brief Private destructor. */ ~CardDatabaseManager() = default; - /// Static card preference provider pointer (default: Noop) + /** @brief Static card preference provider pointer (default: Noop). */ static ICardPreferenceProvider *cardPreferenceProvider; - /// Static path provider pointer (default: Noop) + /** @brief Static path provider pointer (default: Noop). */ static ICardDatabasePathProvider *pathProvider; - /// Static set priority controller pointer (default: Noop) + /** @brief Static set priority controller pointer (default: Noop). */ static ICardSetPriorityController *setPriorityController; }; diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 26e515a2d..174943333 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -36,8 +36,9 @@ QList CardDatabaseQuerier::getCardInfos(const QStringList &cardName QList cardInfos; for (const QString &cardName : cardNames) { CardInfoPtr ptr = db->cards.value(cardName); - if (ptr) + if (ptr) { cardInfos.append(ptr); + } } return cardInfos; @@ -50,10 +51,12 @@ CardInfoPtr CardDatabaseQuerier::getCardBySimpleName(const QString &cardName) co CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const { - if (auto info = getCardInfo(name)) + if (auto info = getCardInfo(name)) { return info; - if (auto info = getCardBySimpleName(name)) + } + if (auto info = getCardBySimpleName(name)) { return info; + } return getCardBySimpleName(CardInfo::simplifyName(name)); } @@ -71,8 +74,9 @@ QList CardDatabaseQuerier::getCards(const QList &cardRefs) c QList cards; for (const auto &cardRef : cardRefs) { ExactCard card = getCard(cardRef); - if (card) + if (card) { cards.append(card); + } } return cards; @@ -119,8 +123,9 @@ ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const ExactCard CardDatabaseQuerier::getRandomCard() const { - if (db->cards.isEmpty()) + if (db->cards.isEmpty()) { return {}; + } const auto keys = db->cards.keys(); int randomIndex = QRandomGenerator::global()->bounded(keys.size()); @@ -133,7 +138,7 @@ ExactCard CardDatabaseQuerier::getRandomCard() const ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const { // The source card does not have a printing defined, which means we can't get a card from the same set. - if (otherPrinting == PrintingInfo()) { + if (otherPrinting.isEmpty()) { return getCard({cardName}); } @@ -360,4 +365,4 @@ QMap CardDatabaseQuerier::getAllFormatsWithCount() const } return formatCounts; -} \ No newline at end of file +} diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp index cc0220526..96a5ac104 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -306,8 +306,9 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) PrintingInfo printingInfo(set); for (QXmlStreamAttribute attr : attrs) { QString attrName = attr.name().toString(); - if (attrName == "picURL") + if (attrName == "picURL") { attrName = "picurl"; + } printingInfo.setProperty(attrName, attr.value().toString()); } diff --git a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h index 16f2359ab..8778ab17e 100644 --- a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h +++ b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h @@ -51,16 +51,21 @@ enum class CardMatchType // convert string to enum inline CardMatchType matchTypeFromString(const QString &str) { - if (str == "equals") + if (str == "equals") { return CardMatchType::Equals; - if (str == "notEquals") + } + if (str == "notEquals") { return CardMatchType::NotEquals; - if (str == "contains") + } + if (str == "contains") { return CardMatchType::Contains; - if (str == "notContains") + } + if (str == "notContains") { return CardMatchType::NotContains; - if (str == "regex") + } + if (str == "regex") { return CardMatchType::Regex; + } return CardMatchType::Equals; // fallback default } diff --git a/libcockatrice_card/libcockatrice/card/game_specific_terms.h b/libcockatrice_card/libcockatrice/card/game_specific_terms.h index 2931365ad..e9160e514 100644 --- a/libcockatrice_card/libcockatrice/card/game_specific_terms.h +++ b/libcockatrice_card/libcockatrice/card/game_specific_terms.h @@ -1,8 +1,8 @@ /** * @file game_specific_terms.h * @ingroup Cards - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef GAME_SPECIFIC_TERMS_H #define GAME_SPECIFIC_TERMS_H @@ -31,26 +31,36 @@ QString const ColorIdentity("coloridentity"); inline static const QString getNicePropertyName(QString key) { - if (key == CardType) + if (key == CardType) { return QCoreApplication::translate("Mtg", "Card Type"); - if (key == ConvertedManaCost) + } + if (key == ConvertedManaCost) { return QCoreApplication::translate("Mtg", "Mana Value"); - if (key == Colors) + } + if (key == Colors) { return QCoreApplication::translate("Mtg", "Color(s)"); - if (key == Loyalty) + } + if (key == Loyalty) { return QCoreApplication::translate("Mtg", "Loyalty"); - if (key == MainCardType) + } + if (key == MainCardType) { return QCoreApplication::translate("Mtg", "Main Card Type"); - if (key == ManaCost) + } + if (key == ManaCost) { return QCoreApplication::translate("Mtg", "Mana Cost"); - if (key == PowTough) + } + if (key == PowTough) { return QCoreApplication::translate("Mtg", "P/T"); - if (key == Side) + } + if (key == Side) { return QCoreApplication::translate("Mtg", "Side"); - if (key == Layout) + } + if (key == Layout) { return QCoreApplication::translate("Mtg", "Layout"); - if (key == ColorIdentity) + } + if (key == ColorIdentity) { return QCoreApplication::translate("Mtg", "Color Identity"); + } return key; } } // namespace Mtg diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index 43d82a9cb..ad7b33654 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -54,6 +54,16 @@ public: return this->set == other.set && this->properties == other.properties; } + /** + * @brief check if the info is empty, as if default constructed. + * + * @return True if both set and properties are empty, otherwise false. + */ + bool isEmpty() const + { + return set == nullptr && properties.isEmpty(); + } + private: CardSetPtr set; ///< The set this variation belongs to. QVariantHash properties; ///< Key-value store for variation-specific attributes. diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.cpp b/libcockatrice_card/libcockatrice/card/set/card_set.cpp index 20d0aced8..6eea220bb 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.cpp +++ b/libcockatrice_card/libcockatrice/card/set/card_set.cpp @@ -33,28 +33,9 @@ QString CardSet::getCorrectedShortName() const { // For Windows machines. QSet invalidFileNames; - invalidFileNames << "CON" - << "PRN" - << "AUX" - << "NUL" - << "COM1" - << "COM2" - << "COM3" - << "COM4" - << "COM5" - << "COM6" - << "COM7" - << "COM8" - << "COM9" - << "LPT1" - << "LPT2" - << "LPT3" - << "LPT4" - << "LPT5" - << "LPT6" - << "LPT7" - << "LPT8" - << "LPT9"; + invalidFileNames << "CON" << "PRN" << "AUX" << "NUL" << "COM1" << "COM2" << "COM3" << "COM4" << "COM5" << "COM6" + << "COM7" << "COM8" << "COM9" << "LPT1" << "LPT2" << "LPT3" << "LPT4" << "LPT5" << "LPT6" << "LPT7" + << "LPT8" << "LPT9"; return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; } diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.h b/libcockatrice_card/libcockatrice/card/set/card_set.h index fe4b66522..8e16e01df 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set.h @@ -107,31 +107,31 @@ public: */ [[nodiscard]] QString getCorrectedShortName() const; - /// @return Short identifier of the set. + /** @return Short identifier of the set. */ [[nodiscard]] QString getShortName() const { return shortName; } - /// @return Descriptive name of the set. + /** @return Descriptive name of the set. */ [[nodiscard]] QString getLongName() const { return longName; } - /// @return Type/category string of the set. + /** @return Type/category string of the set. */ [[nodiscard]] QString getSetType() const { return setType; } - /// @return Release date of the set. + /** @return Release date of the set. */ [[nodiscard]] QDate getReleaseDate() const { return releaseDate; } - /// @return Priority level of the set. + /** @return Priority level of the set. */ [[nodiscard]] Priority getPriority() const { return priority; @@ -181,7 +181,16 @@ public: */ void loadSetOptions(); - /// @return The sort key assigned to this set. + void setSortKeyInMemory(unsigned int _sortKey) + { + sortKey = _sortKey; + } + void setEnabledInMemory(bool _enabled) + { + enabled = _enabled; + } + + /** @return The sort key assigned to this set. */ [[nodiscard]] int getSortKey() const { return sortKey; @@ -193,7 +202,7 @@ public: */ void setSortKey(unsigned int _sortKey); - /// @return True if the set is enabled. + /** @return True if the set is enabled. */ [[nodiscard]] bool getEnabled() const { return enabled; @@ -205,7 +214,7 @@ public: */ void setEnabled(bool _enabled); - /// @return True if the set is considered known. + /** @return True if the set is considered known. */ [[nodiscard]] bool getIsKnown() const { return isknown; diff --git a/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h b/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h index 96f1052a9..0caa9f156 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h @@ -1,8 +1,8 @@ /** * @file card_set_comparator.h * @ingroup CardSets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SET_PRIORITY_COMPARATOR_H #define SET_PRIORITY_COMPARATOR_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index e3e7b41c0..d41713302 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -143,12 +143,14 @@ bool DeckList::loadFromXml(QXmlStreamReader *xml) while (!xml->atEnd()) { xml->readNext(); if (xml->isStartElement()) { - if (xml->name().toString() != "cockatrice_deck") + if (xml->name().toString() != "cockatrice_deck") { return false; + } while (!xml->atEnd()) { xml->readNext(); - if (!readElement(xml)) + if (!readElement(xml)) { break; + } } } } @@ -291,8 +293,9 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, for (; index < max_line; ++index) { // check if line is a card match = reCardLine.match(inputs.at(index)); - if (!match.hasMatch()) + if (!match.hasMatch()) { continue; + } QString cardName = match.captured().simplified(); bool sideboard = false; @@ -305,8 +308,9 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, cardName = match.captured(1); } } else { - if (index == sBStart) + if (index == sBStart) { continue; + } sideboard = index > sBStart; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index a96adeb38..e792c85dc 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -89,7 +89,7 @@ private: mutable QString cachedDeckHash; public: - /// @name Metadata setters + /** @name Metadata setters */ ///@{ void setName(const QString &_name = QString()) { @@ -125,11 +125,11 @@ public: } ///@} - /// @brief Construct an empty deck. + /** @brief Construct an empty deck. */ explicit DeckList(); - /// @brief Construct from a serialized native-format string. + /** @brief Construct from a serialized native-format string. */ explicit DeckList(const QString &nativeString); - /// @brief Construct from components + /** @brief Construct from components. */ DeckList(const Metadata &metadata, const DecklistNodeTree &tree, const QMap &sideboardPlans = {}); @@ -144,8 +144,10 @@ public: return &tree; } - /// @name Metadata getters - /// The individual metadata getters still exist for backwards compatibility. + /** + * @name Metadata getters + * The individual metadata getters still exist for backwards compatibility. + */ ///@{ //! \todo Figure out when we can remove them. const Metadata &getMetadata() const @@ -183,7 +185,7 @@ public: return metadata.isEmpty() && getCardList().isEmpty(); } - /// @name Sideboard plans + /** @name Sideboard plans */ ///@{ QList getCurrentSideboardPlan() const; void setCurrentSideboardPlan(const QList &plan); @@ -193,7 +195,7 @@ public: } ///@} - /// @name Serialization (XML) + /** @name Serialization (XML) */ ///@{ bool readElement(QXmlStreamReader *xml); void write(QXmlStreamWriter *xml) const; @@ -204,7 +206,7 @@ public: bool saveToFile_Native(QIODevice *device) const; ///@} - /// @name Serialization (Plain text) + /** @name Serialization (Plain text) */ ///@{ bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata, @@ -216,7 +218,7 @@ public: QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false) const; ///@} - /// @name Deck manipulation + /** @name Deck manipulation */ ///@{ void cleanList(bool preserveMetadata = false); bool isEmpty() const @@ -238,7 +240,7 @@ public: const bool formatLegal = true); ///@} - /// @name Deck identity + /** @name Deck identity */ ///@{ QString getDeckHash() const; void refreshDeckHash(); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp index 83c9cc0bb..acf4707ab 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp @@ -16,8 +16,9 @@ void DeckListHistoryManager::clear() void DeckListHistoryManager::undo(DeckList *deck) { - if (undoStack.isEmpty()) + if (undoStack.isEmpty()) { return; + } // Peek at the memento we are going to restore const DeckListMemento &mementoToRestore = undoStack.top(); @@ -35,8 +36,9 @@ void DeckListHistoryManager::undo(DeckList *deck) void DeckListHistoryManager::redo(DeckList *deck) { - if (redoStack.isEmpty()) + if (redoStack.isEmpty()) { return; + } // Peek at the memento we are going to restore const DeckListMemento &mementoToRestore = redoStack.top(); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp index 644e0851a..196416cde 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -58,8 +58,9 @@ QList DecklistNodeTree::getZoneNodes(const QSet zones; for (auto *node : *root) { InnerDecklistNode *currentZone = dynamic_cast(node); - if (!currentZone) + if (!currentZone) { continue; + } if (!restrictToZones.isEmpty() && !restrictToZones.contains(currentZone->getName())) { continue; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h index 6de760634..8ef0b18a5 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -12,11 +12,11 @@ class DecklistNodeTree InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). public: - /// @brief Constructs an empty DecklistNodeTree + /** @brief Constructs an empty DecklistNodeTree. */ explicit DecklistNodeTree(); - /// @brief Copy constructor. Deep copies the tree + /** @brief Copy constructor. Deep copies the tree. */ explicit DecklistNodeTree(const DecklistNodeTree &other); - /// @brief Copy-assignment operator. Deep copies the tree + /** @brief Copy-assignment operator. Deep copies the tree. */ DecklistNodeTree &operator=(const DecklistNodeTree &other); virtual ~DecklistNodeTree(); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp index d991ec98e..a76fed619 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp @@ -18,28 +18,30 @@ bool SideboardPlan::readElement(QXmlStreamReader *xml) xml->readNext(); const QString childName = xml->name().toString(); if (xml->isStartElement()) { - if (childName == "name") + if (childName == "name") { name = xml->readElementText(); - else if (childName == "move_card_to_zone") { + } else if (childName == "move_card_to_zone") { MoveCard_ToZone m; while (!xml->atEnd()) { xml->readNext(); const QString childName2 = xml->name().toString(); if (xml->isStartElement()) { - if (childName2 == "card_name") + if (childName2 == "card_name") { m.set_card_name(xml->readElementText().toStdString()); - else if (childName2 == "start_zone") + } else if (childName2 == "start_zone") { m.set_start_zone(xml->readElementText().toStdString()); - else if (childName2 == "target_zone") + } else if (childName2 == "target_zone") { m.set_target_zone(xml->readElementText().toStdString()); + } } else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) { moveList.append(m); break; } } } - } else if (xml->isEndElement() && (childName == "sideboard_plan")) + } else if (xml->isEndElement() && (childName == "sideboard_plan")) { return true; + } } return false; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h index 524217c2d..fff4ff2f3 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h @@ -51,19 +51,19 @@ public: */ void write(QXmlStreamWriter *xml) const; - /// @return The plan name. + /** @return The plan name. */ [[nodiscard]] QString getName() const { return name; } - /// @return Const reference to the move list. + /** @return Const reference to the move list. */ [[nodiscard]] const QList &getMoveList() const { return moveList; } - /// @brief Replace the move list with a new one. + /** @brief Replace the move list with a new one. */ void setMoveList(const QList &_moveList); }; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp index b8a497c20..705dfae4c 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp @@ -38,8 +38,9 @@ bool AbstractDecklistCardNode::readElement(QXmlStreamReader *xml) { while (!xml->atEnd()) { xml->readNext(); - if (xml->isEndElement() && xml->name().toString() == "card") + if (xml->isEndElement() && xml->name().toString() == "card") { return false; + } } return true; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h index 88d8b0930..df903a168 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h @@ -58,40 +58,40 @@ public: { } - /// @return The number of copies of this card in the deck. + /** @return The number of copies of this card in the deck. */ [[nodiscard]] virtual int getNumber() const = 0; - /// @param _number Set the number of copies of this card. + /** @param _number Set the number of copies of this card. */ virtual void setNumber(int _number) = 0; - /// @return The display name of this card. + /** @return The display name of this card. */ [[nodiscard]] QString getName() const override = 0; - /// @param _name Set the display name of this card. + /** @param _name Set the display name of this card. */ virtual void setName(const QString &_name) = 0; - /// @return The provider identifier for this card (e.g., UUID). + /** @return The provider identifier for this card (e.g., UUID). */ [[nodiscard]] virtual QString getCardProviderId() const override = 0; - /// @param _cardProviderId Set the provider identifier for this card. + /** @param _cardProviderId Set the provider identifier for this card. */ virtual void setCardProviderId(const QString &_cardProviderId) = 0; - /// @return The abbreviated set code (e.g., "NEO"). + /** @return The abbreviated set code (e.g., "NEO"). */ [[nodiscard]] virtual QString getCardSetShortName() const override = 0; - /// @param _cardSetShortName Set the abbreviated set code. + /** @param _cardSetShortName Set the abbreviated set code. */ virtual void setCardSetShortName(const QString &_cardSetShortName) = 0; - /// @return The collector number of the card within its set. + /** @return The collector number of the card within its set. */ [[nodiscard]] virtual QString getCardCollectorNumber() const override = 0; - /// @param _cardSetNumber Set the collector number. + /** @param _cardSetNumber Set the collector number. */ virtual void setCardCollectorNumber(const QString &_cardSetNumber) = 0; - /// @return The format legality of the card + /** @return The format legality of the card. */ virtual bool getFormatLegality() const = 0; - /// @param _formatLegal If the card is considered legal + /** @param _formatLegal If the card is considered legal. */ virtual void setFormatLegality(const bool _formatLegal) = 0; /** diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h index 877705705..a39f0e7b2 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h @@ -101,7 +101,7 @@ public: */ explicit AbstractDecklistNode(InnerDecklistNode *_parent = nullptr, int position = -1); - /// Virtual destructor. Child classes must clean up their resources. + /** @brief Virtual destructor. Child classes must clean up their resources. */ virtual ~AbstractDecklistNode() = default; /** @@ -136,7 +136,7 @@ public: */ [[nodiscard]] virtual bool isDeckHeader() const = 0; - /// @return The parent node, or nullptr if this is the root. + /** @return The parent node, or nullptr if this is the root. */ [[nodiscard]] InnerDecklistNode *getParent() const { return parent; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h index b3d42b89a..3bab5405a 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h @@ -93,79 +93,79 @@ public: */ explicit DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent); - /// @return The quantity of this card. + /** @return The quantity of this card. */ [[nodiscard]] int getNumber() const override { return number; } - /// @param _number Set the quantity of this card. + /** @param _number Set the quantity of this card. */ void setNumber(int _number) override { number = _number; } - /// @return The display name of this card. + /** @return The display name of this card. */ [[nodiscard]] QString getName() const override { return name; } - /// @param _name Set the display name of this card. + /** @param _name Set the display name of this card. */ void setName(const QString &_name) override { name = _name; } - /// @return The provider identifier for this card. + /** @return The provider identifier for this card. */ [[nodiscard]] QString getCardProviderId() const override { return cardProviderId; } - /// @param _providerId Set the provider identifier for this card. + /** @param _providerId Set the provider identifier for this card. */ void setCardProviderId(const QString &_providerId) override { cardProviderId = _providerId; } - /// @return The short set code (e.g., "NEO"). + /** @return The short set code (e.g., "NEO"). */ [[nodiscard]] QString getCardSetShortName() const override { return cardSetShortName; } - /// @param _cardSetShortName Set the short set code. + /** @param _cardSetShortName Set the short set code. */ void setCardSetShortName(const QString &_cardSetShortName) override { cardSetShortName = _cardSetShortName; } - /// @return The collector number of this card within its set. + /** @return The collector number of this card within its set. */ [[nodiscard]] QString getCardCollectorNumber() const override { return cardSetNumber; } - /// @param _cardSetNumber Set the collector number. + /** @param _cardSetNumber Set the collector number. */ void setCardCollectorNumber(const QString &_cardSetNumber) override { cardSetNumber = _cardSetNumber; } - /// @return The format legality of the card + /** @return The format legality of the card. */ [[nodiscard]] bool getFormatLegality() const override { return formatLegal; } - /// @param _formatLegal If the card is considered legal + /** @param _formatLegal If the card is considered legal. */ void setFormatLegality(const bool _formatLegal) override { formatLegal = _formatLegal; } - /// @return Always false; card nodes are not deck headers. + /** @return Always false; card nodes are not deck headers. */ [[nodiscard]] bool isDeckHeader() const override { return false; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp index eca58963a..602ea6aec 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp @@ -48,8 +48,9 @@ QString InnerDecklistNode::getVisibleName() const void InnerDecklistNode::clearTree() { - for (int i = 0; i < size(); i++) + for (int i = 0; i < size(); i++) { delete at(i); + } clear(); } @@ -154,8 +155,9 @@ bool InnerDecklistNode::readElement(QXmlStreamReader *xml) xml->attributes().value("collectorNumber").toString(), xml->attributes().value("uuid").toString()); newCard->readElement(xml); } - } else if (xml->isEndElement() && (childName == "zone")) + } else if (xml->isEndElement() && (childName == "zone")) { return false; + } } return true; } @@ -164,8 +166,9 @@ void InnerDecklistNode::writeElement(QXmlStreamWriter *xml) { xml->writeStartElement("zone"); xml->writeAttribute("name", name); - for (int i = 0; i < size(); i++) + for (int i = 0; i < size(); i++) { at(i)->writeElement(xml); + } xml->writeEndElement(); // zone } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h index f4c48afce..81c37dffa 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h @@ -18,11 +18,11 @@ #include "abstract_deck_list_node.h" -/// Constant for the "main" deck zone name. +/** @brief Constant for the "main" deck zone name. */ #define DECK_ZONE_MAIN "main" -/// Constant for the "sideboard" zone name. +/** @brief Constant for the "sideboard" zone name. */ #define DECK_ZONE_SIDE "side" -/// Constant for the "tokens" zone name. +/** @brief Constant for the "tokens" zone name. */ #define DECK_ZONE_TOKENS "tokens" /** @@ -93,13 +93,13 @@ public: */ void setSortMethod(DeckSortMethod method) override; - /// @return The internal name of this node. + /** @return The internal name of this node. */ [[nodiscard]] QString getName() const override { return name; } - /// @param _name Set the internal name of this node. + /** @param _name Set the internal name of this node. */ void setName(const QString &_name) { name = _name; @@ -122,25 +122,25 @@ public: */ [[nodiscard]] virtual QString getVisibleName() const; - /// @return Always empty for container nodes. + /** @return Always empty for container nodes. */ [[nodiscard]] QString getCardProviderId() const override { return ""; } - /// @return Always empty for container nodes. + /** @return Always empty for container nodes. */ [[nodiscard]] QString getCardSetShortName() const override { return ""; } - /// @return Always empty for container nodes. + /** @return Always empty for container nodes. */ [[nodiscard]] QString getCardCollectorNumber() const override { return ""; } - /// @return Always true; InnerDecklistNode represents deck structure. + /** @return Always true; InnerDecklistNode represents deck structure. */ [[nodiscard]] bool isDeckHeader() const override { return true; @@ -196,10 +196,10 @@ public: */ bool compare(AbstractDecklistNode *other) const override; - /// @copydoc compare(AbstractDecklistNode*) const + /** @copydoc compare(AbstractDecklistNode*) const */ bool compareNumber(AbstractDecklistNode *other) const; - /// @copydoc compare(AbstractDecklistNode*) const + /** @copydoc compare(AbstractDecklistNode*) const */ bool compareName(AbstractDecklistNode *other) const; /** diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index 732e43a09..bb1e0ef53 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -1,8 +1,8 @@ /** * @file filter_card.h * @ingroup CardDatabaseModelFilters - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDFILTER_H #define CARDFILTER_H diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index 704e8fadb..25e8e97db 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -251,20 +251,27 @@ static void setupParserRules() const auto arg = std::any_cast(sv[1]); const auto op = std::any_cast(sv[0]); - if (op == ">") + if (op == ">") { return [=](const int s) { return s > arg; }; - if (op == ">=") + } + if (op == ">=") { return [=](const int s) { return s >= arg; }; - if (op == "<") + } + if (op == "<") { return [=](const int s) { return s < arg; }; - if (op == "<=") + } + if (op == "<=") { return [=](const int s) { return s <= arg; }; - if (op == "=") + } + if (op == "=") { return [=](const int s) { return s == arg; }; - if (op == ":") + } + if (op == ":") { return [=](const int s) { return s == arg; }; - if (op == "!=") + } + if (op == "!=") { return [=](const int s) { return s != arg; }; + } return [](int) { return false; }; }; @@ -315,8 +322,9 @@ static void setupParserRules() return true; } - if (parts.contains("c") && match.length() == 0) + if (parts.contains("c") && match.length() == 0) { return true; + } auto containsColor = [&parts](const QString &s) { return parts.contains(s); }; return std::any_of(match.begin(), match.end(), containsColor); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h index 8fc41ce68..71a99f7b5 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.h @@ -1,8 +1,8 @@ /** * @file filter_string.h * @ingroup CardDatabaseModelFilters - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef FILTER_STRING_H #define FILTER_STRING_H diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 19e8c2d8d..8502db50b 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -205,8 +205,9 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const */ int match_count = 0; for (auto &it : converted_term) { - if (info->getColors().contains(it, Qt::CaseInsensitive)) + if (info->getColors().contains(it, Qt::CaseInsensitive)) { match_count++; + } } return match_count == converted_term.length(); @@ -542,12 +543,14 @@ void FilterTree::removeFilter(const CardFilter *toRemove) { for (int i = childNodes.size() - 1; i >= 0; --i) { auto *logicMap = dynamic_cast(childNodes.at(i)); - if (!logicMap || logicMap->attr != toRemove->attr()) + if (!logicMap || logicMap->attr != toRemove->attr()) { continue; + } FilterItemList *typeList = logicMap->typeList(toRemove->type()); - if (!typeList) + if (!typeList) { continue; + } int termIdx = typeList->termIndex(toRemove->term()); if (termIdx != -1) { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 75d4241a5..aac1777e0 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -1,8 +1,8 @@ /** * @file filter_tree.h * @ingroup CardDatabaseModelFilters - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef FILTERTREE_H #define FILTERTREE_H @@ -70,28 +70,33 @@ public: } virtual void nodeChanged() const { - if (parent() != nullptr) + if (parent() != nullptr) { parent()->nodeChanged(); + } } virtual void preInsertChild(const FilterTreeNode *p, int i) const { - if (parent() != nullptr) + if (parent() != nullptr) { parent()->preInsertChild(p, i); + } } virtual void postInsertChild(const FilterTreeNode *p, int i) const { - if (parent() != nullptr) + if (parent() != nullptr) { parent()->postInsertChild(p, i); + } } virtual void preRemoveChild(const FilterTreeNode *p, int i) const { - if (parent() != nullptr) + if (parent() != nullptr) { parent()->preRemoveChild(p, i); + } } virtual void postRemoveChild(const FilterTreeNode *p, int i) const { - if (parent() != nullptr) + if (parent() != nullptr) { parent()->postRemoveChild(p, i); + } } }; diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index b8fbbc74a..9559967af 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -6,6 +6,13 @@ class ICardSetPriorityController { public: + struct SetSaveData + { + QString shortName; + unsigned int sortKey; + bool enabled; + }; + virtual ~ICardSetPriorityController() = default; virtual void setSortKey(QString shortName, unsigned int sortKey) = 0; @@ -15,6 +22,8 @@ public: virtual unsigned int getSortKey(QString shortName) const = 0; virtual bool isEnabled(QString shortName) const = 0; virtual bool isKnown(QString shortName) const = 0; + + virtual void saveSets(const QVector &data) = 0; }; #endif // COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h index e5027648c..188e2ced9 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h @@ -28,6 +28,10 @@ public: { return true; } + + void saveSets(const QVector & /* data */) override + { + } }; #endif // COCKATRICE_NOOP_CARD_SET_PRIORITY_CONTROLLER_H diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h index afb6f1fcf..91dc06335 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h @@ -1,8 +1,8 @@ /** * @file card_completer_proxy_model.h * @ingroup CardDatabaseModels - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_COMPLETER_PROXY_MODEL_H #define CARD_COMPLETER_PROXY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp index 6a930c1da..d1fbbac2f 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp @@ -19,8 +19,9 @@ int CardSearchModel::rowCount(const QModelIndex &parent) const QVariant CardSearchModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= searchResults.size()) + if (!index.isValid() || index.row() >= searchResults.size()) { return QVariant(); + } if (role == Qt::DisplayRole) { return searchResults.at(index.row()).card->getName(); @@ -34,8 +35,9 @@ void CardSearchModel::updateSearchResults(const QString &query) beginResetModel(); searchResults.clear(); - if (query.isEmpty() || !sourceModel) + if (query.isEmpty() || !sourceModel) { return; + } // Set the filter for the display model sourceModel->setCardName(query); @@ -46,13 +48,15 @@ void CardSearchModel::updateSearchResults(const QString &query) QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex); CardDatabaseModel *sourceDbModel = qobject_cast(sourceModel->sourceModel()); - if (!sourceDbModel || !sourceIndex.isValid()) + if (!sourceDbModel || !sourceIndex.isValid()) { return; + } CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row()); - if (!card) + if (!card) { continue; + } int distance = levenshteinDistance(query.toLower(), card->getName().toLower()); searchResults.append({card, distance}); @@ -63,8 +67,9 @@ void CardSearchModel::updateSearchResults(const QString &query) [](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; }); // Keep only the top 5 results - if (searchResults.size() > 10) + if (searchResults.size() > 10) { searchResults = searchResults.mid(0, 10); + } emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); emit layoutChanged(); diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h index 18be2c55a..bc4be7a0e 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h @@ -1,8 +1,8 @@ /** * @file card_search_model.h * @ingroup CardDatabaseModels - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARD_SEARCH_MODEL_H #define CARD_SEARCH_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp index 5ce63a939..724ee61f2 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp @@ -10,11 +10,28 @@ CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) setSortCaseSensitivity(Qt::CaseInsensitive); dirtyTimer.setSingleShot(true); - connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate); + connect(&dirtyTimer, &QTimer::timeout, this, [this]() { + invalidate(); + emit modelDirty(); + }); loadedRowCount = 0; } +void CardDatabaseDisplayModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + + connect(model, &QAbstractItemModel::rowsInserted, this, [this]() { dirty(); }); + + connect(model, &QAbstractItemModel::rowsRemoved, this, [this]() { dirty(); }); + + connect(model, &QAbstractItemModel::modelReset, this, [this]() { + loadedRowCount = 0; + dirty(); + }); +} + QMap CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'}, {L'”', L'\"'}, {L'‘', L'\''}, @@ -58,12 +75,14 @@ bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelInd // test for an exact match: isLeftType && leftString.size() == cardName.size() // or an exclusive start match: isLeftType && !isRightType - if (isLeftType && (!isRightType || leftString.size() == cardName.size())) + if (isLeftType && (!isRightType || leftString.size() == cardName.size())) { return true; + } // same checks for the right string - if (isRightType && (!isLeftType || rightString.size() == cardName.size())) + if (isRightType && (!isLeftType || rightString.size() == cardName.size())) { return false; + } } else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) { QStringList leftList = leftString.split("/"); QStringList rightList = rightString.split("/"); @@ -155,8 +174,9 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex { CardInfoPtr info = static_cast(sourceModel())->getCard(sourceRow); - if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken())) + if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken())) { return false; + } if (filterString != nullptr) { if (filterTree != nullptr && !filterTree->acceptsCard(info)) { @@ -170,14 +190,17 @@ bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const { - if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) + if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) { return false; + } - if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName())) + if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName())) { return false; + } - if (filterTree != nullptr) + if (filterTree != nullptr) { return filterTree->acceptsCard(info); + } return true; } @@ -191,8 +214,9 @@ void CardDatabaseDisplayModel::clearFilterAll() cardText.clear(); cardTypes.clear(); cardColors.clear(); - if (filterTree != nullptr) + if (filterTree != nullptr) { filterTree->clear(); + } #if (QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)) endFilterChange(QSortFilterProxyModel::Direction::Rows); #else @@ -202,8 +226,9 @@ void CardDatabaseDisplayModel::clearFilterAll() void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree) { - if (this->filterTree != nullptr) + if (this->filterTree != nullptr) { disconnect(this->filterTree, nullptr, this, nullptr); + } this->filterTree = _filterTree; connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged); diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index 0c5994a3a..c3145c356 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -38,11 +38,11 @@ private: public: explicit CardDatabaseDisplayModel(QObject *parent = nullptr); + void setSourceModel(QAbstractItemModel *model) override; void setFilterTree(FilterTree *_filterTree); void setIsToken(FilterBool _isToken) { isToken = _isToken; - emit modelDirty(); dirty(); } @@ -53,20 +53,17 @@ public: filterString = nullptr; } cardName = sanitizeCardName(_cardName, characterTranslation); - emit modelDirty(); dirty(); } void setStringFilter(const QString &_src) { delete filterString; filterString = new FilterString(_src); - emit modelDirty(); dirty(); } void setCardNameSet(const QSet &_cardNameSet) { cardNameSet = _cardNameSet; - emit modelDirty(); dirty(); } diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp index e33156329..253dcd134 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp @@ -31,8 +31,9 @@ int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS || - (role != Qt::DisplayRole && role != SortRole)) + (role != Qt::DisplayRole && role != SortRole)) { return QVariant(); + } CardInfoPtr card = cardList.at(index.row()); switch (index.column()) { @@ -56,10 +57,12 @@ QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) + if (role != Qt::DisplayRole) { return QVariant(); - if (orientation != Qt::Horizontal) + } + if (orientation != Qt::Horizontal) { return QVariant(); + } switch (section) { case NameColumn: return QString(tr("Name")); @@ -78,24 +81,27 @@ QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, } } -void CardDatabaseModel::cardInfoChanged(CardInfoPtr card) +void CardDatabaseModel::cardInfoChanged(const CardInfoPtr &card) { const int row = cardList.indexOf(card); - if (row == -1) + if (row == -1) { return; + } emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1)); } -bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card) +bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(const CardInfoPtr &card) const { - if (!showOnlyCardsFromEnabledSets) + if (!showOnlyCardsFromEnabledSets) { return true; + } for (const auto &printings : card->getSets()) { for (const auto &printing : printings) { - if (printing.getSet()->getEnabled()) + if (printing.getSet()->getEnabled()) { return true; + } } } @@ -119,7 +125,7 @@ void CardDatabaseModel::cardDatabaseEnabledSetsChanged() } } -void CardDatabaseModel::cardAdded(CardInfoPtr card) +void CardDatabaseModel::cardAdded(const CardInfoPtr &card) { if (checkCardHasAtLeastOneEnabledSet(card)) { // add the card if it's present in at least one enabled set diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_model.h index 218cfff92..8655389d7 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_model.h @@ -51,11 +51,11 @@ private: CardDatabase *db; bool showOnlyCardsFromEnabledSets; - inline bool checkCardHasAtLeastOneEnabledSet(CardInfoPtr card); + inline bool checkCardHasAtLeastOneEnabledSet(const CardInfoPtr &card) const; private slots: - void cardAdded(CardInfoPtr card); + void cardAdded(const CardInfoPtr &card); void cardRemoved(CardInfoPtr card); - void cardInfoChanged(CardInfoPtr card); + void cardInfoChanged(const CardInfoPtr &card); void cardDatabaseEnabledSetsChanged(); }; diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp index f6dc4f9cf..5e0cc31d8 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp @@ -6,8 +6,9 @@ SetsModel::SetsModel(CardDatabase *_db, QObject *parent) : QAbstractTableModel(p { sets.sortByKey(); for (const CardSetPtr &set : sets) { - if (set->getEnabled()) + if (set->getEnabled()) { enabledSets.insert(set); + } } } @@ -15,16 +16,18 @@ SetsModel::~SetsModel() = default; int SetsModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) + if (parent.isValid()) { return 0; - else + } else { return sets.size(); + } } QVariant SetsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount())) + if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount())) { return QVariant(); + } CardSetPtr set = sets[index.row()]; @@ -40,8 +43,9 @@ QVariant SetsModel::data(const QModelIndex &index, int role) const } } - if (role != Qt::DisplayRole && role != SortRole) + if (role != Qt::DisplayRole && role != SortRole) { return QVariant(); + } switch (index.column()) { case SortKeyCol: @@ -72,8 +76,9 @@ bool SetsModel::setData(const QModelIndex &index, const QVariant &value, int rol QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const { - if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) + if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) { return QVariant(); + } switch (section) { case SortKeyCol: return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */ @@ -97,13 +102,15 @@ QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int rol Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return Qt::NoItemFlags; + } Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - if (index.column() == EnabledCol) + if (index.column() == EnabledCol) { flags |= Qt::ItemIsUserCheckable; + } return flags; } @@ -115,8 +122,9 @@ Qt::DropActions SetsModel::supportedDropActions() const QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return 0; + } SetsMimeData *result = new SetsMimeData(indexes[0].row()); return qobject_cast(result); @@ -128,16 +136,19 @@ bool SetsModel::dropMimeData(const QMimeData *data, int /*column*/, const QModelIndex &parent) { - if (action != Qt::MoveAction) + if (action != Qt::MoveAction) { return false; + } if (row == -1) { - if (!parent.isValid()) + if (!parent.isValid()) { return false; + } row = parent.row(); } int oldRow = qobject_cast(data)->getOldRow(); - if (oldRow < row) + if (oldRow < row) { row--; + } swapRows(oldRow, row); @@ -148,10 +159,11 @@ void SetsModel::toggleRow(int row, bool enable) { CardSetPtr temp = sets.at(row); - if (enable) + if (enable) { enabledSets.insert(temp); - else + } else { enabledSets.remove(temp); + } emit dataChanged(index(row, EnabledCol), index(row, EnabledCol)); } @@ -160,13 +172,15 @@ void SetsModel::toggleRow(int row) { CardSetPtr tmp = sets.at(row); - if (tmp == nullptr) + if (tmp == nullptr) { return; + } - if (enabledSets.contains(tmp)) + if (enabledSets.contains(tmp)) { enabledSets.remove(tmp); - else + } else { enabledSets.insert(tmp); + } emit dataChanged(index(row, EnabledCol), index(row, EnabledCol)); } @@ -175,9 +189,11 @@ void SetsModel::toggleAll(bool enabled) { enabledSets.clear(); - if (enabled) - for (CardSetPtr set : sets) + if (enabled) { + for (CardSetPtr set : sets) { enabledSets.insert(set); + } + } emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } @@ -208,8 +224,9 @@ void SetsModel::sort(int column, Qt::SortOrder order) int numRows = rowCount(); int row; - for (row = 0; row < numRows; ++row) + for (row = 0; row < numRows; ++row) { setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row)); + } QList tmp = setMap.values(); sets.clear(); @@ -228,16 +245,19 @@ void SetsModel::sort(int column, Qt::SortOrder order) void SetsModel::save(CardDatabase *db) { - // order - for (int i = 0; i < sets.size(); i++) - sets[i]->setSortKey(static_cast(i + 1)); + QVector saveData; + saveData.reserve(sets.size()); - // enabled sets - for (const CardSetPtr &set : sets) - set->setEnabled(enabledSets.contains(set)); + for (int i = 0; i < sets.size(); ++i) { + const unsigned int sortKey = static_cast(i + 1); + const bool enabled = enabledSets.contains(sets[i]); + sets[i]->setSortKeyInMemory(sortKey); + sets[i]->setEnabledInMemory(enabled); + saveData.append({sets[i]->getShortName(), sortKey, enabled}); + } + db->getPriorityController()->saveSets(saveData); sets.sortByKey(); - db->notifyEnabledSetsChanged(); } @@ -250,8 +270,9 @@ void SetsModel::restore(CardDatabase *db) // enabled sets enabledSets.clear(); for (const CardSetPtr &set : sets) { - if (set->getEnabled()) + if (set->getEnabled()) { enabledSets.insert(set); + } } emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h index 0ffc5a847..2adf71def 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h @@ -1,8 +1,8 @@ /** * @file sets_model.h * @ingroup CardDatabaseModels - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SETSMODEL_H #define SETSMODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h index f6b2fdfbb..c8f767189 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h @@ -1,8 +1,8 @@ /** * @file token_display_model.h * @ingroup CardDatabaseModels - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_TOKEN_DISPLAY_MODEL_H #define COCKATRICE_TOKEN_DISPLAY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h index 5e5843761..92dfaadd7 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h @@ -1,8 +1,8 @@ /** * @file token_edit_model.h * @ingroup CardDatabaseModels - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_TOKEN_EDIT_MODEL_H #define COCKATRICE_TOKEN_EDIT_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index e43c612f0..9b43281c1 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -66,7 +66,7 @@ void DeckListModel::rebuildTree() for (int j = 0; j < currentZone->size(); j++) { auto *currentCard = dynamic_cast(currentZone->at(j)); - // TODO: better sanity checking + //! \todo Better sanity checking. if (currentCard == nullptr) { continue; } @@ -257,8 +257,9 @@ Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent) { int rows = rowCount(parent); - if (rows == 0) + if (rows == 0) { return; + } QModelIndex topLeft = index(0, 0, parent); QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent); @@ -539,8 +540,9 @@ int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const Ca for (int i = 0; i < parent->size(); ++i) { auto *existingCard = dynamic_cast(parent->at(i)); - if (!existingCard) + if (!existingCard) { continue; + } bool lessThan = false; switch (lastKnownColumn) { @@ -557,8 +559,9 @@ int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const Ca break; } - if (lessThan) + if (lessThan) { return i; + } } return parent->size(); // insert at end if no earlier match diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h index 86d36b7f9..209ec8c42 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -89,12 +89,15 @@ static inline QString toString(Type t) static inline Type fromString(const QString &s) { - if (s == "Main Type") + if (s == "Main Type") { return MAIN_TYPE; - if (s == "Mana Cost") + } + if (s == "Mana Cost") { return MANA_COST; - if (s == "Colors") + } + if (s == "Colors") { return COLOR; + } return MAIN_TYPE; // default } } // namespace DeckListModelGroupCriteria @@ -427,8 +430,9 @@ private: template T getNode(const QModelIndex &index) const { - if (!index.isValid()) + if (!index.isValid()) { return dynamic_cast(root); + } return dynamic_cast(static_cast(index.internalPointer())); } diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp index 0ec159737..3d433153a 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp @@ -29,25 +29,29 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode QString ln = lNode->getName(); QString rn = rNode->getName(); int cmp = ln.localeAwareCompare(rn); - if (cmp != 0) + if (cmp != 0) { return cmp < 0; + } } else if (crit == "cmc") { int lc = lInfo ? lInfo->getCmc().toInt() : 0; int rc = rInfo ? rInfo->getCmc().toInt() : 0; - if (lc != rc) + if (lc != rc) { return lc < rc; + } } else if (crit == "colors") { QString lr = lInfo ? lInfo->getColors() : QString(); QString rr = rInfo ? rInfo->getColors() : QString(); int cmp = lr.localeAwareCompare(rr); - if (cmp != 0) + if (cmp != 0) { return cmp < 0; + } } else if (crit == "maintype") { QString lr = lInfo ? lInfo->getMainCardType() : QString(); QString rr = rInfo ? rInfo->getMainCardType() : QString(); int cmp = lr.localeAwareCompare(rr); - if (cmp != 0) + if (cmp != 0) { return cmp < 0; + } } } diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h index 94742795d..1565417f0 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h @@ -1,8 +1,8 @@ /** * @file deck_list_sort_filter_proxy_model.h * @ingroup DeckEditorCardGroupWidgets - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H #define COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp index 11458768d..916f4351b 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp @@ -66,8 +66,9 @@ void AbstractClient::processProtocolItem(const ServerMessage &item) const int cmdId = response.cmd_id(); PendingCommand *pend = pendingCommands.value(cmdId, 0); - if (!pend) + if (!pend) { return; + } pendingCommands.remove(cmdId); pend->processResponse(response); diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h index dc3be5a94..2eb7e3356 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h @@ -1,8 +1,8 @@ /** * @file abstract_client.h * @ingroup Client - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef ABSTRACTCLIENT_H #define ABSTRACTCLIENT_H diff --git a/libcockatrice_network/libcockatrice/network/client/local/local_client.h b/libcockatrice_network/libcockatrice/network/client/local/local_client.h index e8c5330ac..9e1c89ddd 100644 --- a/libcockatrice_network/libcockatrice/network/client/local/local_client.h +++ b/libcockatrice_network/libcockatrice/network/client/local/local_client.h @@ -1,8 +1,8 @@ /** * @file local_client.h * @ingroup Client - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LOCALCLIENT_H #define LOCALCLIENT_H diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 3e3ec889d..7e20f2722 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -213,8 +213,9 @@ Command_Login RemoteClient::generateCommandLogin() if (!clientFeatures.isEmpty()) { QMap::iterator i; - for (i = clientFeatures.begin(); i != clientFeatures.end(); ++i) + for (i = clientFeatures.begin(); i != clientFeatures.end(); ++i) { cmdLogin.add_clientfeatures(i.key().toStdString().c_str()); + } } return cmdLogin; @@ -223,14 +224,14 @@ Command_Login RemoteClient::generateCommandLogin() void RemoteClient::doLogin() { if (!password.isEmpty() && serverSupportsPasswordHash) { - // TODO store and log in using stored hashed password + //! \todo Store and log in using stored hashed password. if (hashedPassword.isEmpty()) { doRequestPasswordSalt(); // ask salt to create hashedPassword, then log in } else { doHashedLogin(); // log in using hashed password instead } } else { - // TODO add setting for client to reject unhashed logins + //! \todo Add setting for client to reject unhashed logins. setStatus(StatusLoggingIn); Command_Login cmdLogin = generateCommandLogin(); if (!password.isEmpty()) { @@ -284,8 +285,9 @@ void RemoteClient::loginResponse(const Response &response) QString possibleMissingFeatures; if (resp.missing_features_size() > 0) { - for (int i = 0; i < resp.missing_features_size(); ++i) + for (int i = 0; i < resp.missing_features_size(); ++i) { possibleMissingFeatures.append("," + QString::fromStdString(resp.missing_features(i))); + } } if (response.response_code() == Response::RespOk) { @@ -293,13 +295,15 @@ void RemoteClient::loginResponse(const Response &response) emit userInfoChanged(resp.user_info()); QList buddyList; - for (int i = resp.buddy_list_size() - 1; i >= 0; --i) + for (int i = resp.buddy_list_size() - 1; i >= 0; --i) { buddyList.append(resp.buddy_list(i)); + } emit buddyListReceived(buddyList); QList ignoreList; - for (int i = resp.ignore_list_size() - 1; i >= 0; --i) + for (int i = resp.ignore_list_size() - 1; i >= 0; --i) { ignoreList.append(resp.ignore_list(i)); + } emit ignoreListReceived(ignoreList); if (newMissingFeatureFound(possibleMissingFeatures) && resp.missing_features_size() > 0 && @@ -311,8 +315,9 @@ void RemoteClient::loginResponse(const Response &response) } else if (response.response_code() != Response::RespNotConnected) { QList missingFeatures; if (resp.missing_features_size() > 0) { - for (int i = 0; i < resp.missing_features_size(); ++i) + for (int i = 0; i < resp.missing_features_size(); ++i) { missingFeatures << QString::fromStdString(resp.missing_features(i)); + } } emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), static_cast(resp.denied_end_time()), missingFeatures); @@ -383,11 +388,13 @@ void RemoteClient::readData() inputBuffer.remove(0, 4); messageInProgress = true; } - } else + } else { return; + } } - if (inputBuffer.size() < messageLength) + if (inputBuffer.size() < messageLength) { return; + } ServerMessage newServerMessage; bool ok = newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); @@ -403,8 +410,9 @@ void RemoteClient::readData() qCDebug(RemoteClientLog) << "parsing error!"; } - if (getStatus() == StatusDisconnecting) // use thread-safe getter + if (getStatus() == StatusDisconnecting) { // use thread-safe getter doDisconnectFromServer(); + } } while (!inputBuffer.isEmpty()); } @@ -537,8 +545,9 @@ void RemoteClient::doDisconnectFromServer() pendingCommands.clear(); setStatus(StatusDisconnected); - if (websocket->isValid()) + if (websocket->isValid()) { websocket->close(); + } socket->close(); } @@ -615,8 +624,9 @@ bool RemoteClient::newMissingFeatureFound(const QString &_serversMissingFeatures QStringList serversMissingFeaturesList = _serversMissingFeatures.split(","); for (const QString &feature : serversMissingFeaturesList) { if (!feature.isEmpty()) { - if (!networkSettingsProvider->getKnownMissingFeatures().contains(feature)) + if (!networkSettingsProvider->getKnownMissingFeatures().contains(feature)) { return true; + } } } return newMissingFeature; @@ -628,8 +638,9 @@ void RemoteClient::clearNewClientFeatures() QStringList existingKnownMissingFeatures = networkSettingsProvider->getKnownMissingFeatures().split(","); for (const QString &existingKnownFeature : existingKnownMissingFeatures) { if (!existingKnownFeature.isEmpty()) { - if (!clientFeatures.contains(existingKnownFeature)) + if (!clientFeatures.contains(existingKnownFeature)) { newKnownMissingFeatures.append("," + existingKnownFeature); + } } } networkSettingsProvider->setKnownMissingFeatures(newKnownMissingFeatures); @@ -667,10 +678,12 @@ void RemoteClient::requestForgotPasswordResponse(const Response &response) if (response.response_code() == Response::RespOk) { if (resp.challenge_email()) { emit sigPromptForForgotPasswordChallenge(); - } else + } else { emit sigPromptForForgotPasswordReset(); - } else + } + } else { emit sigForgotPasswordError(); + } doDisconnectFromServer(); } @@ -698,8 +711,9 @@ void RemoteClient::submitForgotPasswordResetResponse(const Response &response) { if (response.response_code() == Response::RespOk) { emit sigForgotPasswordSuccess(); - } else + } else { emit sigForgotPasswordError(); + } doDisconnectFromServer(); } @@ -732,8 +746,9 @@ void RemoteClient::submitForgotPasswordChallengeResponse(const Response &respons { if (response.response_code() == Response::RespOk) { emit sigPromptForForgotPasswordReset(); - } else + } else { emit sigForgotPasswordError(); + } doDisconnectFromServer(); } diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h index 15e3e8ef5..289fdc5d0 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h @@ -1,8 +1,8 @@ /** * @file remote_client.h * @ingroup Client - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef REMOTECLIENT_H #define REMOTECLIENT_H diff --git a/libcockatrice_network/libcockatrice/network/server/local/local_server.h b/libcockatrice_network/libcockatrice/network/server/local/local_server.h index 70586f6c1..0112afa72 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/local_server.h +++ b/libcockatrice_network/libcockatrice/network/server/local/local_server.h @@ -1,8 +1,8 @@ /** * @file local_server.h * @ingroup Server - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LOCALSERVER_H #define LOCALSERVER_H diff --git a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h index 4410fd65c..44e3b3e53 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h @@ -1,8 +1,8 @@ /** * @file local_server_interface.h * @ingroup Server - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LOCALSERVERINTERFACE_H #define LOCALSERVERINTERFACE_H diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index f04bcc849..157fa6441 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -49,6 +49,8 @@ #include #include #include +#include +#include Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, int _playerId, @@ -79,17 +81,6 @@ int Server_AbstractPlayer::newCardId() return nextCardId++; } -int Server_AbstractPlayer::newArrowId() const -{ - int id = 0; - for (Server_Arrow *a : arrows) { - if (a->getId() > id) { - id = a->getId(); - } - } - return id + 1; -} - void Server_AbstractPlayer::setupZones() { nextCardId = 0; @@ -228,6 +219,37 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ return false; } +/** + * @brief Determines whether a set of moved cards is from the bottom of the deck + */ +static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set &cardsToMove) +{ + if (!startZone) { + return false; + } + + if (startZone->getName() != ZoneNames::DECK) { + return false; + } + + int movedCount = static_cast(cardsToMove.size()); + int tailStart = startZone->getCards().size() - movedCount; + if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom + return false; + } + + // check if the move is a contiguous block at the end of the deck, fail fast when not + int expectedPosition = tailStart; + for (const auto &card : cardsToMove) { + if (card.position != expectedPosition) { + return false; + } + ++expectedPosition; + } + + return true; +} + Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, @@ -244,8 +266,11 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespContextError; } - if (!targetzone->hasCoords() && (xCoord <= -1)) { - xCoord = targetzone->getCards().size(); + if (!targetzone->hasCoords()) { + yCoord = 0; + if (xCoord <= -1) { + xCoord = targetzone->getCards().size(); + } } std::set cardsToMove; @@ -285,164 +310,21 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, bool revealTopStart = false; bool revealTopTarget = false; - for (auto cardStruct : cardsToMove) { - Server_Card *card = cardStruct.card; - int originalPosition = cardStruct.position; + bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove); - bool sourceBeingLookedAt; - int position = startzone->removeCard(card, sourceBeingLookedAt); - - // Attachment relationships can be retained when moving a card onto the opponent's table - if (startzone->getName() != targetzone->getName()) { - // Delete all attachment relationships - if (card->getParentCard()) { - card->setParentCard(nullptr); - } - - // Make a copy of the list because the original one gets modified during the loop - QList attachedCards = card->getAttachedCards(); - for (auto &attachedCard : attachedCards) { - attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); - } + if (isFromBottom) { + std::ranges::reverse_view reversedCardsToMove{cardsToMove}; + for (auto card : reversedCardsToMove) { + processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, + isReversed, undoingDraw); } - - if (startzone != targetzone) { - // Delete all arrows from and to the card - for (auto *player : game->getPlayers().values()) { - QList arrowsToDelete; - for (Server_Arrow *arrow : player->getArrows()) { - if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) - arrowsToDelete.append(arrow->getId()); - } - for (int j : arrowsToDelete) { - player->deleteArrow(j); - } - } - } - - if (shouldDestroyOnMove(card, startzone, targetzone)) { - Event_DestroyCard event; - event.set_zone_name(startzone->getName().toStdString()); - event.set_card_id(static_cast(card->getId())); - ges.enqueueGameEvent(event, playerId); - - if (Server_Card *stashedCard = card->takeStashedCard()) { - stashedCard->setId(newCardId()); - ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), - playerId); - card->deleteLater(); - card = stashedCard; - } else { - card->deleteLater(); - card = nullptr; - } - } - - if (card) { - ++xIndex; - int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; - - bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); - - if (targetzone->hasCoords()) { - newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); - } else { - yCoord = 0; - card->resetState(targetzone->getName() == ZoneNames::STACK); - } - - targetzone->insertCard(card, newX, yCoord); - int targetLookedCards = targetzone->getCardsBeingLookedAt(); - bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); - if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { - if (sourceKnownToPlayer) { - targetLookedCards += 1; - } else { - targetLookedCards = newX; - } - targetzone->setCardsBeingLookedAt(targetLookedCards); - } - - bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); - bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); - - int oldCardId = card->getId(); - if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { - card->setId(targetzone->getPlayer()->newCardId()); - } - card->setFaceDown(faceDown); - - Event_MoveCard eventOthers; - eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); - eventOthers.set_start_zone(startzone->getName().toStdString()); - eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); - if (startzone != targetzone) { - eventOthers.set_target_zone(targetzone->getName().toStdString()); - } - eventOthers.set_y(yCoord); - eventOthers.set_face_down(faceDown); - - Event_MoveCard eventPrivate(eventOthers); - if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || - startzone->getType() != ServerInfo_Zone::HiddenZone) { - eventPrivate.set_card_id(oldCardId); - eventPrivate.set_new_card_id(card->getId()); - } else { - eventPrivate.set_card_id(-1); - eventPrivate.set_new_card_id(-1); - } - if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { - QString privateCardName = card->getName(); - eventPrivate.set_card_name(privateCardName.toStdString()); - eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); - } - if (startzone->getType() == ServerInfo_Zone::HiddenZone) { - eventPrivate.set_position(position); - } else { - eventPrivate.set_position(-1); - } - - eventPrivate.set_x(newX); - - if ( - // cards from public zones have their id known, their previous position is already known, the event does - // not accomodate for previous locations in zones with coordinates (which are always public) - (startzone->getType() != ServerInfo_Zone::PublicZone) && - // other players are not allowed to be able to track which card is which in private zones like the hand - (startzone->getType() != ServerInfo_Zone::PrivateZone)) { - eventOthers.set_position(position); - } - if ( - // other players are not allowed to be able to track which card is which in private zones like the hand - (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { - eventOthers.set_x(newX); - } - - if ((startzone->getType() == ServerInfo_Zone::PublicZone) || - (targetzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_card_id(oldCardId); - if (!(sourceHiddenToOthers && targetHiddenToOthers)) { - QString publicCardName = card->getName(); - eventOthers.set_card_name(publicCardName.toStdString()); - eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); - } - eventOthers.set_new_card_id(card->getId()); - } - - ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); - ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); - - if (originalPosition == 0) { - revealTopStart = true; - } - if (newX == 0) { - revealTopTarget = true; - } - - // handle side effects for this card - onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); + } else { + for (auto card : cardsToMove) { + processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, + isReversed, undoingDraw); } } + if (revealTopStart) { revealTopCardIfNeeded(startzone, ges); } @@ -462,6 +344,178 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespOk; } +void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges, + Server_CardZone *startzone, + Server_CardZone *targetzone, + MoveCardStruct cardStruct, + int xCoord, + int yCoord, + int &xIndex, + bool &revealTopStart, + bool &revealTopTarget, + bool isReversed, + bool undoingDraw) +{ + Server_Card *card = cardStruct.card; + int originalPosition = cardStruct.position; + + bool sourceBeingLookedAt; + int position = startzone->removeCard(card, sourceBeingLookedAt); + + // Attachment relationships can be retained when moving a card onto the opponent's table + if (startzone->getName() != targetzone->getName()) { + // Delete all attachment relationships + if (card->getParentCard()) { + card->setParentCard(nullptr); + } + + // Make a copy of the list because the original one gets modified during the loop + QList attachedCards = card->getAttachedCards(); + for (auto &attachedCard : attachedCards) { + attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); + } + } + + if (startzone != targetzone) { + // Delete all arrows from and to the card + for (auto *player : game->getPlayers().values()) { + QList arrowsToDelete; + for (Server_Arrow *arrow : player->getArrows()) { + if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) { + arrowsToDelete.append(arrow->getId()); + } + } + for (int j : arrowsToDelete) { + Event_DeleteArrow event; + event.set_arrow_id(j); + ges.enqueueGameEvent(event, player->getPlayerId()); + player->deleteArrow(j); + } + } + } + + if (shouldDestroyOnMove(card, startzone, targetzone)) { + Event_DestroyCard event; + event.set_zone_name(startzone->getName().toStdString()); + event.set_card_id(static_cast(card->getId())); + ges.enqueueGameEvent(event, playerId); + + if (Server_Card *stashedCard = card->takeStashedCard()) { + stashedCard->setId(newCardId()); + ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), playerId); + card->deleteLater(); + card = stashedCard; + } else { + card->deleteLater(); + card = nullptr; + } + } + + if (card) { + ++xIndex; + int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; + + bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); + + if (targetzone->hasCoords()) { + newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); + } else { + card->resetState(targetzone->getName() == ZoneNames::STACK); + } + + targetzone->insertCard(card, newX, yCoord); + int targetLookedCards = targetzone->getCardsBeingLookedAt(); + bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); + if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { + if (sourceKnownToPlayer) { + targetLookedCards += 1; + } else { + targetLookedCards = newX; + } + targetzone->setCardsBeingLookedAt(targetLookedCards); + } + + bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); + bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); + + int oldCardId = card->getId(); + if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { + card->setId(targetzone->getPlayer()->newCardId()); + } + card->setFaceDown(faceDown); + + Event_MoveCard eventOthers; + eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); + eventOthers.set_start_zone(startzone->getName().toStdString()); + eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); + if (startzone != targetzone) { + eventOthers.set_target_zone(targetzone->getName().toStdString()); + } + eventOthers.set_y(yCoord); + eventOthers.set_face_down(faceDown); + + Event_MoveCard eventPrivate(eventOthers); + if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || + startzone->getType() != ServerInfo_Zone::HiddenZone) { + eventPrivate.set_card_id(oldCardId); + eventPrivate.set_new_card_id(card->getId()); + } else { + eventPrivate.set_card_id(-1); + eventPrivate.set_new_card_id(-1); + } + if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { + QString privateCardName = card->getName(); + eventPrivate.set_card_name(privateCardName.toStdString()); + eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); + } + if (startzone->getType() == ServerInfo_Zone::HiddenZone) { + eventPrivate.set_position(position); + } else { + eventPrivate.set_position(-1); + } + + eventPrivate.set_x(newX); + + if ( + // cards from public zones have their id known, their previous position is already known, the event does + // not accomodate for previous locations in zones with coordinates (which are always public) + (startzone->getType() != ServerInfo_Zone::PublicZone) && + // other players are not allowed to be able to track which card is which in private zones like the hand + (startzone->getType() != ServerInfo_Zone::PrivateZone)) { + eventOthers.set_position(position); + } + if ( + // other players are not allowed to be able to track which card is which in private zones like the hand + (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { + eventOthers.set_x(newX); + } + + if ((startzone->getType() == ServerInfo_Zone::PublicZone) || + (targetzone->getType() == ServerInfo_Zone::PublicZone)) { + eventOthers.set_card_id(oldCardId); + if (!(sourceHiddenToOthers && targetHiddenToOthers)) { + QString publicCardName = card->getName(); + eventOthers.set_card_name(publicCardName.toStdString()); + eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); + } + eventOthers.set_new_card_id(card->getId()); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + if (originalPosition == 0) { + revealTopStart = true; + } + if (newX == 0) { + revealTopTarget = true; + } + + // handle side effects for this card + onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); + } +} + void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, @@ -1030,8 +1084,9 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo _event.set_zone_name(card->getZone()->getName().toStdString()); _event.set_card_id(card->getId()); - card->setCounter(i.key(), i.value(), &_event); - ges.enqueueGameEvent(_event, playerId); + if (card->setCounter(i.key(), i.value(), &_event)) { + ges.enqueueGameEvent(_event, playerId); + } } // Copy parent card @@ -1069,12 +1124,18 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo targetItem = card; } if (sendGameEvent) { - Event_CreateArrow _event; - ServerInfo_Arrow *arrowInfo = _event.mutable_arrow_info(); - changedArrowIds.append(arrow->getId()); - int id = player->newArrowId(); - arrow->setId(id); - arrowInfo->set_id(id); + const int oldId = arrow->getId(); + changedArrowIds.append(oldId); + + Event_DeleteArrow deleteEvent; + deleteEvent.set_arrow_id(oldId); + ges.enqueueGameEvent(deleteEvent, player->getPlayerId()); + + Event_CreateArrow createEvent; + ServerInfo_Arrow *arrowInfo = createEvent.mutable_arrow_info(); + const int newId = game->generateArrowId(); + arrow->setId(newId); + arrowInfo->set_id(newId); arrowInfo->set_start_player_id(player->getPlayerId()); arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString()); arrowInfo->set_start_card_id(startCard->getId()); @@ -1088,7 +1149,7 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo arrowInfo->set_target_card_id(arrowTargetCard->getId()); } arrowInfo->mutable_arrow_color()->CopyFrom(arrow->getColor()); - ges.enqueueGameEvent(_event, player->getPlayerId()); + ges.enqueueGameEvent(createEvent, player->getPlayerId()); } } for (int id : changedArrowIds) { @@ -1195,7 +1256,8 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo int currentPhase = game->getActivePhase(); int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase; - auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase); + auto arrow = new Server_Arrow(game->generateArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, + deletionPhase); addArrow(arrow); Event_CreateArrow event; @@ -1277,8 +1339,9 @@ Response::ResponseCode Server_AbstractPlayer::cmdSetCardCounter(const Command_Se Event_SetCardCounter event; event.set_zone_name(zone->getName().toStdString()); event.set_card_id(card->getId()); - card->setCounter(cmd.counter_id(), cmd.counter_value(), &event); - ges.enqueueGameEvent(event, playerId); + if (card->setCounter(cmd.counter_id(), cmd.counter_value(), &event)) { + ges.enqueueGameEvent(event, playerId); + } return Response::RespOk; } @@ -1307,14 +1370,13 @@ Response::ResponseCode Server_AbstractPlayer::cmdIncCardCounter(const Command_In return Response::RespNameNotFound; } - int newValue = card->getCounter(cmd.counter_id()) + cmd.counter_delta(); - card->setCounter(cmd.counter_id(), newValue); - Event_SetCardCounter event; event.set_zone_name(zone->getName().toStdString()); event.set_card_id(card->getId()); - event.set_counter_id(cmd.counter_id()); - event.set_counter_value(newValue); + if (!card->incrementCounter(cmd.counter_id(), cmd.counter_delta(), &event)) { + return Response::RespOk; + } + ges.enqueueGameEvent(event, playerId); return Response::RespOk; @@ -1412,8 +1474,9 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo if (cmd.has_player_id()) { Server_AbstractPlayer *otherPlayer = game->getPlayer(cmd.player_id()); - if (!otherPlayer) + if (!otherPlayer) { return Response::RespNameNotFound; + } } Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name())); if (!zone) { @@ -1502,7 +1565,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo zone->addWritePermission(cmd.player_id()); } - if (isJudge()) { + if (judge) { ges.setOverwriteOwnership(true); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index 40fe84aa1..85fbc0557 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -74,7 +74,6 @@ public: } int newCardId(); - int newArrowId() const; void addZone(Server_CardZone *zone); void addArrow(Server_Arrow *arrow); @@ -93,6 +92,19 @@ public: bool fixFreeSpaces = true, bool undoingDraw = false, bool isReversed = false); + + void processMoveCard(GameEventStorage &ges, + Server_CardZone *startzone, + Server_CardZone *targetzone, + MoveCardStruct cardStruct, + int xCoord, + int yCoord, + int &xIndex, + bool &revealTopStart, + bool &revealTopTarget, + bool isReversed, + bool undoingDraw); + virtual void onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp index f6787baa2..2ad38b977 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp @@ -30,6 +30,7 @@ void Server_Arrow::getInfo(ServerInfo_Arrow *info) info->set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerId()); info->set_target_zone(targetCard->getZone()->getName().toStdString()); info->set_target_card_id(targetCard->getId()); - } else + } else { info->set_target_player_id(static_cast(targetItem)->getPlayerId()); + } } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp index 86ff2f008..b858314c0 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), cardRef(cardRef), tapped(false), attacking(false), @@ -36,11 +38,13 @@ Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coo Server_Card::~Server_Card() { // setParentCard(0) leads to the item being removed from our list, so we can't iterate properly - while (!attachedCards.isEmpty()) + while (!attachedCards.isEmpty()) { attachedCards.first()->setParentCard(0); + } - if (parentCard) + if (parentCard) { parentCard->removeAttachedCard(this); + } if (stashedCard) { stashedCard->deleteLater(); @@ -62,16 +66,18 @@ void Server_Card::resetState(bool keepAnnotations) QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) { - if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntap) + if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntap) { return QVariant(tapped).toString(); + } return setAttribute(attribute, avalue); } QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, Event_SetCardAttr *event) { - if (event) + if (event) { event->set_attribute(attribute); + } switch (attribute) { case AttrTapped: { @@ -89,8 +95,9 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue break; case AttrPT: setPT(avalue); - if (event) + if (event) { event->set_attr_value(getPT().toStdString()); + } return getPT(); case AttrAnnotation: setAnnotation(avalue); @@ -99,31 +106,71 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue setDoesntUntap(avalue == "1"); break; } - if (event) + if (event) { event->set_attr_value(avalue.toStdString()); + } return avalue; } -void Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event) +bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event) { - if (value) + // Clamp to valid card counter range [0, MAX_COUNTERS_ON_CARD] + value = qBound(0, value, MAX_COUNTERS_ON_CARD); + + const int oldValue = counters.value(_id, 0); + if (value == oldValue) { + return false; + } + + if (value) { counters.insert(_id, value); - else + } else { counters.remove(_id); + } if (event) { event->set_counter_id(_id); event->set_counter_value(value); } + + return true; +} + +bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounter *event) +{ + const int oldValue = counters.value(counterId, 0); + const auto result = static_cast(oldValue) + static_cast(delta); + // Clamp to [0, MAX_COUNTERS_ON_CARD] for card counters + const int newValue = + static_cast(qBound(static_cast(0), result, static_cast(MAX_COUNTERS_ON_CARD))); + + if (newValue == oldValue) { + return false; + } + + if (newValue) { + counters.insert(counterId, newValue); + } else { + counters.remove(counterId); + } + + if (event) { + event->set_counter_id(counterId); + event->set_counter_value(newValue); + } + + return true; } void Server_Card::setParentCard(Server_Card *_parentCard) { - if (parentCard) + if (parentCard) { parentCard->removeAttachedCard(this); + } parentCard = _parentCard; - if (parentCard) + if (parentCard) { parentCard->addAttachedCard(this); + } } void Server_Card::getInfo(ServerInfo_Card *info) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h index bc326bbc4..3d7e649b9 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h @@ -153,7 +153,24 @@ public: { cardRef = _cardRef; } - void setCounter(int _id, int value, Event_SetCardCounter *event = nullptr); + /** + * @brief Sets a card counter to an exact value with clamping. + * @param _id The counter ID. + * @param value The desired value (clamped to [0, MAX_COUNTERS_ON_CARD]; 0 removes the counter). + * @param event Optional event to populate with counter state. + * @return true if the value changed, false otherwise. + */ + [[nodiscard]] bool setCounter(int _id, int value, Event_SetCardCounter *event = nullptr); + /** + * @brief Increments a card counter with overflow-safe arithmetic. + * @param counterId The counter ID to modify. + * @param delta The amount to add (may be negative for decrement). + * @param event Optional event to populate with counter state. + * @return true if the value changed, false otherwise. + * @note If counter does not exist, starts from 0. Counter is removed if result is 0. + * @note Clamps result to [0, MAX_COUNTERS_ON_CARD]. + */ + [[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr); void setTapped(bool _tapped) { tapped = _tapped; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp index f2a35e548..1db4f9c4c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp @@ -47,19 +47,23 @@ void Server_CardZone::shuffle(int start, int end) cardsBeingLookedAt = 0; // Size 0 or 1 decks are sorted - if (cards.size() < 2) + if (cards.size() < 2) { return; + } // Negative numbers signify positions starting at the end of the // zone convert these to actual indexes. - if (end < 0) + if (end < 0) { end += cards.size(); + } - if (start < 0) + if (start < 0) { start += cards.size(); + } - if (start < 0 || end < 0 || start >= cards.size() || end >= cards.size()) + if (start < 0 || end < 0 || start >= cards.size() || end >= cards.size()) { return; + } for (int i = end; i > start; i--) { int j = rng->rand(start, i); @@ -70,40 +74,47 @@ void Server_CardZone::shuffle(int start, int end) void Server_CardZone::removeCardFromCoordMap(Server_Card *card, int oldX, int oldY) { - if (oldX < 0) + if (oldX < 0) { return; + } const int baseX = (oldX / 3) * 3; QMap &coordMap = coordinateMap[oldY]; - if (coordMap.contains(baseX) && coordMap.contains(baseX + 1) && coordMap.contains(baseX + 2)) + if (coordMap.contains(baseX) && coordMap.contains(baseX + 1) && coordMap.contains(baseX + 2)) { // If the removal of this card has opened up a previously full pile... freePilesMap[oldY].insert(coordMap.value(baseX)->getName(), baseX); + } coordMap.remove(oldX); if (!(coordMap.contains(baseX) && coordMap.value(baseX)->getName() == card->getName()) && !(coordMap.contains(baseX + 1) && coordMap.value(baseX + 1)->getName() == card->getName()) && - !(coordMap.contains(baseX + 2) && coordMap.value(baseX + 2)->getName() == card->getName())) + !(coordMap.contains(baseX + 2) && coordMap.value(baseX + 2)->getName() == card->getName())) { // If this card was the last one with this name... freePilesMap[oldY].remove(card->getName(), baseX); + } if (!coordMap.contains(baseX) && !coordMap.contains(baseX + 1) && !coordMap.contains(baseX + 2)) { // If the removal of this card has freed a whole pile, i.e. it was the last card in it... - if (baseX < freeSpaceMap[oldY]) + if (baseX < freeSpaceMap[oldY]) { freeSpaceMap[oldY] = baseX; + } } } void Server_CardZone::insertCardIntoCoordMap(Server_Card *card, int x, int y) { - if (x < 0) + if (x < 0) { return; + } coordinateMap[y].insert(x, card); if (!(x % 3)) { - if (!card->getFaceDown() && !freePilesMap[y].contains(card->getName(), x) && card->getAttachedCards().isEmpty()) + if (!card->getFaceDown() && !freePilesMap[y].contains(card->getName(), x) && + card->getAttachedCards().isEmpty()) { freePilesMap[y].insert(card->getName(), x); + } if (freeSpaceMap[y] == x) { int nextFreeX = x; do { @@ -146,8 +157,9 @@ Server_Card *Server_CardZone::getCard(int id, int *position, bool remove) for (int i = 0; i < cards.size(); ++i) { Server_Card *tmp = cards[i]; if (tmp->getId() == id) { - if (position) + if (position) { *position = i; + } if (remove) { cards.removeAt(i); tmp->setZone(nullptr); @@ -157,11 +169,13 @@ Server_Card *Server_CardZone::getCard(int id, int *position, bool remove) } return nullptr; } else { - if ((id >= cards.size()) || (id < 0)) + if ((id >= cards.size()) || (id < 0)) { return nullptr; + } Server_Card *tmp = cards[id]; - if (position) + if (position) { *position = id; + } if (remove) { cards.removeAt(id); tmp->setZone(nullptr); @@ -184,32 +198,35 @@ int Server_CardZone::getFreeGridColumn(int x, int y, const QString &cardName, bo if (coordMap.contains(x) && (coordMap[x]->getFaceDown() || !coordMap[x]->getAttachedCards().isEmpty())) { // don't pile up on: 1. facedown cards 2. cards with attached cards - } else if (!coordMap.contains(x)) + } else if (!coordMap.contains(x)) { return x; - else if (!coordMap.contains(x + 1)) + } else if (!coordMap.contains(x + 1)) { return x + 1; - else + } else { return x + 2; + } } } else if (x >= 0) { int resultX = 0; x = (x / 3) * 3; - if (!coordMap.contains(x)) + if (!coordMap.contains(x)) { resultX = x; - else if (!coordMap.value(x)->getAttachedCards().isEmpty()) { + } else if (!coordMap.value(x)->getAttachedCards().isEmpty()) { resultX = x; x = -1; - } else if (!coordMap.contains(x + 1)) + } else if (!coordMap.contains(x + 1)) { resultX = x + 1; - else if (!coordMap.contains(x + 2)) + } else if (!coordMap.contains(x + 2)) { resultX = x + 2; - else { + } else { resultX = x; x = -1; } - if (x < 0) - while (coordMap.contains(resultX)) + if (x < 0) { + while (coordMap.contains(resultX)) { resultX += 3; + } + } return resultX; } @@ -219,16 +236,18 @@ int Server_CardZone::getFreeGridColumn(int x, int y, const QString &cardName, bo bool Server_CardZone::isColumnStacked(int x, int y) const { - if (!has_coords) + if (!has_coords) { return false; + } return coordinateMap[y].contains((x / 3) * 3 + 1); } bool Server_CardZone::isColumnEmpty(int x, int y) const { - if (!has_coords) + if (!has_coords) { return true; + } return !coordinateMap[y].contains((x / 3) * 3); } @@ -243,12 +262,14 @@ void Server_CardZone::moveCardInRow(GameEventStorage &ges, Server_Card *card, in void Server_CardZone::fixFreeSpaces(GameEventStorage &ges) { - if (!has_coords) + if (!has_coords) { return; + } QSet> placesToLook; - for (auto &card : cards) + for (auto &card : cards) { placesToLook.insert(QPair((card->getX() / 3) * 3, card->getY())); + } QSetIterator> placeIterator(placesToLook); while (placeIterator.hasNext()) { @@ -257,26 +278,30 @@ void Server_CardZone::fixFreeSpaces(GameEventStorage &ges) int y = foo.second; if (!coordinateMap[y].contains(baseX)) { - if (coordinateMap[y].contains(baseX + 1)) + if (coordinateMap[y].contains(baseX + 1)) { moveCardInRow(ges, coordinateMap[y].value(baseX + 1), baseX, y); - else if (coordinateMap[y].contains(baseX + 2)) { + } else if (coordinateMap[y].contains(baseX + 2)) { moveCardInRow(ges, coordinateMap[y].value(baseX + 2), baseX, y); continue; - } else + } else { continue; + } } - if (!coordinateMap[y].contains(baseX + 1) && coordinateMap[y].contains(baseX + 2)) + if (!coordinateMap[y].contains(baseX + 1) && coordinateMap[y].contains(baseX + 2)) { moveCardInRow(ges, coordinateMap[y].value(baseX + 2), baseX + 1, y); + } } } void Server_CardZone::updateCardCoordinates(Server_Card *card, int oldX, int oldY) { - if (!has_coords) + if (!has_coords) { return; + } - if (oldX != -1) + if (oldX != -1) { removeCardFromCoordMap(card, oldX, oldY); + } insertCardIntoCoordMap(card, card->getX(), card->getY()); } @@ -299,8 +324,9 @@ void Server_CardZone::insertCard(Server_Card *card, int x, int y) void Server_CardZone::clear() { - for (auto card : cards) + for (auto card : cards) { delete card; + } cards.clear(); coordinateMap.clear(); freePilesMap.clear(); @@ -329,7 +355,8 @@ void Server_CardZone::getInfo(ServerInfo_Zone *info, Server_AbstractParticipant const bool zonesOthersCanSee = type == ServerInfo_Zone::PublicZone; if ((selfPlayerAsking && zonesSelfCanSee) || (otherPlayerAsking && zonesOthersCanSee)) { QListIterator cardIterator(cards); - while (cardIterator.hasNext()) + while (cardIterator.hasNext()) { cardIterator.next()->getInfo(info->add_card_list()); + } } } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.cpp index b18e11c2b..e65205cbb 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.cpp @@ -1,12 +1,24 @@ #include "server_counter.h" #include +#include Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count) : id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count) { } +//! \todo Extract overflow-safe arithmetic into shared helper. +//! Duplicated in Server_Card::incrementCounter() - keep in sync if modified. +bool Server_Counter::incrementCount(int delta) +{ + const int oldCount = count; + const auto result = static_cast(count) + static_cast(delta); + count = static_cast(qBound(static_cast(std::numeric_limits::min()), result, + static_cast(std::numeric_limits::max()))); + return count != oldCount; +} + void Server_Counter::getInfo(ServerInfo_Counter *info) { info->set_id(id); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.h index 55aad991c..8226e663f 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_counter.h @@ -25,6 +25,18 @@ class ServerInfo_Counter; +/** + * @class Server_Counter + * @brief Represents a player counter with overflow-safe increment arithmetic. + * + * All value modifications return whether the value actually changed, + * enabling callers to skip unnecessary network events. + * + * @note Direct assignment via setCount() does not clamp; only + * incrementCount() enforces int boundary saturation. + * @note Unlike card counters, player counters are never auto-removed + * when they reach zero - they persist with value 0. + */ class Server_Counter { protected: @@ -59,11 +71,33 @@ public: { return count; } - void setCount(int _count) + + /** + * @brief Sets the counter to an exact value. + * @param _count The new value (assigned directly without clamping). + * @return true if the value changed, false otherwise. + * @warning This performs raw assignment. For overflow-safe incrementing, + * use incrementCount(). + */ + [[nodiscard]] bool setCount(int _count) { + const int oldCount = count; count = _count; + return count != oldCount; } + /** + * @brief Increments the counter by delta with overflow-safe arithmetic. + * @param delta The amount to add (may be negative for decrement). + * @return true if the value changed, false otherwise. + * @note Clamps result to [INT_MIN, INT_MAX] to prevent overflow. + */ + [[nodiscard]] bool incrementCount(int delta); + + /** + * @brief Populates info with this counter's current state for network serialization. + * @param info The protobuf message to populate. + */ void getInfo(ServerInfo_Counter *info); }; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 2224ddb13..4761199e5 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -147,8 +147,9 @@ void Server_Game::storeGameInformation() const QStringList &allGameTypes = room->getGameTypes(); QStringList _gameTypes; - for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) + for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) { _gameTypes.append(allGameTypes[gameInfo.game_types(i)]); + } for (const auto &playerName : allPlayersEver) { replayMatchInfo->add_player_names(playerName.toStdString()); @@ -166,8 +167,9 @@ void Server_Game::storeGameInformation() server->clientsLock.lockForRead(); for (auto userName : allPlayersEver + allSpectatorsEver) { Server_AbstractUserInterface *userHandler = server->findUser(userName); - if (userHandler && server->getStoreReplaysEnabled()) + if (userHandler && server->getStoreReplaysEnabled()) { userHandler->sendProtocolItem(*sessionEvent); + } } server->clientsLock.unlock(); delete sessionEvent; @@ -189,8 +191,9 @@ void Server_Game::pingClockTimeout() bool allPlayersInactive = true; int playerCount = 0; for (auto *participant : participants) { - if (participant == nullptr) + if (participant == nullptr) { continue; + } if (!participant->isSpectator()) { ++playerCount; @@ -253,8 +256,9 @@ int Server_Game::getSpectatorCount() const int result = 0; for (Server_AbstractParticipant *participant : participants.values()) { - if (participant->isSpectator()) + if (participant->isSpectator()) { ++result; + } } return result; } @@ -269,8 +273,9 @@ void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event, event->set_game_started(true); event->set_active_player_id(0); event->set_active_phase(0); - } else + } else { event->set_game_started(false); + } for (Server_AbstractParticipant *participant : participants.values()) { participant->getInfo(event->add_player_list(), recipient, omniscient, withUserInfo); @@ -306,7 +311,7 @@ void Server_Game::sendGameStateToPlayers() } } else { Event_GameStateChanged event; - createGameStateChangedEvent(&event, participant, false, false); + createGameStateChangedEvent(&event, participant, participant->isJudge(), false); gec = prepareGameEvent(event, -1); } @@ -329,7 +334,7 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) if (!player->getReadyStart()) { if (forceStartGame) { // Player is not ready to start, so kick them - // TODO: Move them to Spectators instead + //! \todo Move them to Spectators instead. kickParticipant(player->getPlayerId()); } else { return; @@ -367,8 +372,9 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) delete replayCont; startTimeOfThisGame = secondsElapsed; - } else + } else { firstGameStarted = true; + } sendGameStateToPlayers(); @@ -396,11 +402,13 @@ void Server_Game::stopGameIfFinished() int playing = 0; auto players = getPlayers(); for (auto *player : players.values()) { - if (!player->getConceded()) + if (!player->getConceded()) { ++playing; + } } - if (playing > 1) + if (playing > 1) { return; + } gameStarted = false; @@ -428,32 +436,40 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, { Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); for (auto *participant : participants.values()) { - if (participant->getUserInfo()->name() == user->name()) + if (participant->getUserInfo()->name() == user->name()) { return Response::RespContextError; + } } if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) { return Response::RespUserLevelTooLow; } if (!(overrideRestrictions && (user->user_level() & ServerInfo_User::IsModerator))) { - if ((_password != password) && !(spectator && !spectatorsNeedPassword)) + if ((_password != password) && !(spectator && !spectatorsNeedPassword)) { return Response::RespWrongPassword; - if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) + } + if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) { return Response::RespUserLevelTooLow; - if (onlyBuddies && (user->name() != creatorInfo->name())) + } + if (onlyBuddies && (user->name() != creatorInfo->name())) { if (!databaseInterface->isInBuddyList(QString::fromStdString(creatorInfo->name()), - QString::fromStdString(user->name()))) + QString::fromStdString(user->name()))) { return Response::RespOnlyBuddies; + } + } if (databaseInterface->isInIgnoreList(QString::fromStdString(creatorInfo->name()), - QString::fromStdString(user->name()))) + QString::fromStdString(user->name()))) { return Response::RespInIgnoreList; + } if (spectator) { - if (!spectatorsAllowed) + if (!spectatorsAllowed) { return Response::RespSpectatorsNotAllowed; + } } } - if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) + if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) { return Response::RespGameFull; + } return Response::RespOk; } @@ -463,8 +479,9 @@ bool Server_Game::containsUser(const QString &userName) const QMutexLocker locker(&gameMutex); for (auto *participant : participants.values()) { - if (participant->getUserInfo()->name() == userName.toStdString()) + if (participant->getUserInfo()->name() == userName.toStdString()) { return true; + } } return false; } @@ -500,7 +517,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, allPlayersEver.insert(playerName); // if the original creator of the game joins, give them host status back - //! \todo transferring host to spectators has side effects + //! \todo Transferring host to spectators has side effects. if (newParticipant->getUserInfo()->name() == creatorInfo->name()) { hostId = newParticipant->getPlayerId(); sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); @@ -516,8 +533,9 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, emit gameInfoChanged(gameInfo); } - if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) + if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) { room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newParticipant->getPlayerId()); + } userInterface->playerAddedToGame(gameId, room->getId(), newParticipant->getPlayerId()); @@ -564,8 +582,9 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve } if (!spectator) { stopGameIfFinished(); - if (gameStarted && playerActive) + if (gameStarted && playerActive) { nextTurn(); + } } ServerInfo_Game gameInfo; @@ -588,15 +607,18 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Abst for (auto *arrow : anyPlayer->getArrows().values()) { auto *targetCard = qobject_cast(arrow->getTargetItem()); if (targetCard) { - if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player) + if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player) { toDelete.append(arrow); + } } else if (arrow->getTargetItem() == player) { toDelete.append(arrow); } // Don't use else here! It has to happen regardless of whether targetCard == 0. - if (arrow->getStartCard()->getZone() != nullptr && arrow->getStartCard()->getZone()->getPlayer() == player) + if (arrow->getStartCard()->getZone() != nullptr && + arrow->getStartCard()->getZone()->getPlayer() == player) { toDelete.append(arrow); + } } for (auto *arrow : toDelete) { Event_DeleteArrow event; @@ -635,8 +657,9 @@ bool Server_Game::kickParticipant(int playerId) QMutexLocker locker(&gameMutex); auto *participant = participants.value(playerId); - if (!participant) + if (!participant) { return false; + } GameEventContainer *gec = prepareGameEvent(Event_Kicked(), -1); participant->sendGameEvent(*gec); @@ -674,6 +697,11 @@ void Server_Game::setActivePhase(int newPhase) sendGameEventContainer(prepareGameEvent(event, -1)); } +qint64 Server_Game::generateArrowId() +{ + return nextArrowId++; +} + void Server_Game::removeArrows(int newPhase, bool force) { QMutexLocker locker(&gameMutex); @@ -750,7 +778,7 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti event2.set_active_player_id(activePlayer); event2.set_active_phase(activePhase); - bool omniscient = joiningParticipant->isSpectator() && (spectatorsSeeEverything || joiningParticipant->isJudge()); + bool omniscient = (joiningParticipant->isSpectator() && spectatorsSeeEverything) || joiningParticipant->isJudge(); for (auto *participant : participants.values()) { participant->getInfo(event2.add_player_list(), joiningParticipant, omniscient, true); } @@ -766,11 +794,12 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont, cont->set_game_id(gameId); for (auto *participant : participants.values()) { - const bool playerPrivate = (participant->getPlayerId() == privatePlayerId) || - (participant->isSpectator() && (spectatorsSeeEverything || participant->isJudge())); + const bool playerPrivate = (participant->getPlayerId() == privatePlayerId) || participant->isJudge() || + (participant->isSpectator() && spectatorsSeeEverything); if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || - (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) + (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) { participant->sendGameEvent(*cont); + } } if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { cont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); @@ -786,11 +815,13 @@ Server_Game::prepareGameEvent(const ::google::protobuf::Message &gameEvent, int { auto *cont = new GameEventContainer; cont->set_game_id(gameId); - if (context) + if (context) { cont->mutable_context()->CopyFrom(*context); + } GameEvent *event = cont->add_event_list(); - if (playerId != -1) + if (playerId != -1) { event->set_player_id(playerId); + } event->GetReflection() ->MutableMessage(event, gameEvent.GetDescriptor()->FindExtensionByName("ext")) ->CopyFrom(gameEvent); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index 1c658f2ba..e0e7896b7 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -49,6 +49,7 @@ class Server_Game : public QObject private: Server_Room *room; int nextPlayerId; + std::atomic nextArrowId = 1; int hostId; ServerInfo_User *creatorInfo; QMap participants; @@ -196,6 +197,7 @@ public: } void setActivePlayer(int newPlayer); void setActivePhase(int newPhase); + qint64 generateArrowId(); void removeArrows(int newPhase, bool force = false); void nextTurn(); int getSecondsElapsed() const diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index 1175e4b57..56e3f9f8e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -409,6 +410,9 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer & } if (lastDrawList.isEmpty()) { + Event_GameLogNotice event; + event.set_notice_type(Event_GameLogNotice::UNDO_DRAW_FAILED); + ges.enqueueGameEvent(event, playerId); return Response::RespContextError; } @@ -432,17 +436,19 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & return Response::RespContextError; } - Server_Counter *c = counters.value(cmd.counter_id(), 0); + const int counterId = cmd.counter_id(); + Server_Counter *c = counters.value(counterId, nullptr); if (!c) { return Response::RespNameNotFound; } - c->setCount(c->getCount() + cmd.delta()); - - Event_SetCounter event; - event.set_counter_id(c->getId()); - event.set_value(c->getCount()); - ges.enqueueGameEvent(event, playerId); + bool didChange = c->incrementCount(cmd.delta()); + if (didChange) { + Event_SetCounter event; + event.set_counter_id(c->getId()); + event.set_value(c->getCount()); + ges.enqueueGameEvent(event, playerId); + } return Response::RespOk; } @@ -483,17 +489,19 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer & return Response::RespContextError; } - Server_Counter *c = counters.value(cmd.counter_id(), 0); + const int counterId = cmd.counter_id(); + Server_Counter *c = counters.value(counterId, nullptr); if (!c) { return Response::RespNameNotFound; } - c->setCount(cmd.value()); - - Event_SetCounter event; - event.set_counter_id(c->getId()); - event.set_value(c->getCount()); - ges.enqueueGameEvent(event, playerId); + bool didChange = c->setCount(cmd.value()); + if (didChange) { + Event_SetCounter event; + event.set_counter_id(c->getId()); + event.set_value(c->getCount()); + ges.enqueueGameEvent(event, playerId); + } return Response::RespOk; } @@ -508,15 +516,16 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer & return Response::RespContextError; } - Server_Counter *counter = counters.value(cmd.counter_id(), 0); + const int counterId = cmd.counter_id(); + Server_Counter *counter = counters.value(counterId, nullptr); if (!counter) { return Response::RespNameNotFound; } - counters.remove(cmd.counter_id()); + counters.remove(counterId); delete counter; Event_DelCounter event; - event.set_counter_id(cmd.counter_id()); + event.set_counter_id(counterId); ges.enqueueGameEvent(event, playerId); return Response::RespOk; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp index a5a74c54c..3da9ddc73 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp @@ -56,8 +56,9 @@ void Server::prepareDestroy() { roomsLock.lockForWrite(); QMapIterator roomIterator(rooms); - while (roomIterator.hasNext()) + while (roomIterator.hasNext()) { delete roomIterator.next().value(); + } rooms.clear(); roomsLock.unlock(); } @@ -86,22 +87,25 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, bool hasClientId = false; if (clientid.isEmpty()) { // client id is empty, either out dated client or client has been modified - if (getClientIDRequiredEnabled()) + if (getClientIDRequiredEnabled()) { return ClientIdRequired; + } } else { hasClientId = true; } - if (name.size() > 35) + if (name.size() > 35) { name = name.left(35); + } Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, clientid, reasonStr, secondsLeft, passwordNeedsHash); if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || - authState == UserIsInactive) + authState == UserIsInactive) { return authState; + } ServerInfo_User data = databaseInterface->getUserData(name, true); data.set_address(session->getAddress().toStdString()); @@ -140,8 +144,9 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString tempName = name; int i = 0; while (users.contains(tempName) || databaseInterface->activeUserExists(tempName) || - databaseInterface->userSessionExists(tempName)) + databaseInterface->userSessionExists(tempName)) { tempName = name + "_" + QString::number(++i); + } name = tempName; data.set_name(name.toStdString()); } @@ -163,9 +168,11 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, Event_UserJoined event; event.mutable_user_info()->CopyFrom(session->copyUserInfo(false)); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); - for (auto &client : clients) - if (client->getAcceptsUserListChanges()) + for (auto &client : clients) { + if (client->getAcceptsUserListChanges()) { client->sendProtocolItem(*se); + } + } delete se; event.mutable_user_info()->CopyFrom(session->copyUserInfo(true, true, true)); @@ -206,19 +213,22 @@ Server_AbstractUserInterface *Server::findUser(const QString &userName) const // Call this only with clientsLock set. Server_AbstractUserInterface *userHandler = users.value(userName); - if (userHandler) + if (userHandler) { return userHandler; - else + } else { return externalUsers.value(userName); + } } void Server::addClient(Server_ProtocolHandler *client) { - if (client->getConnectionType() == "tcp") + if (client->getConnectionType() == "tcp") { tcpUserCount++; + } - if (client->getConnectionType() == "websocket") + if (client->getConnectionType() == "websocket") { webSocketUserCount++; + } QWriteLocker locker(&clientsLock); clients << client; @@ -232,11 +242,13 @@ void Server::removeClient(Server_ProtocolHandler *client) return; } - if (client->getConnectionType() == "tcp") + if (client->getConnectionType() == "tcp") { tcpUserCount--; + } - if (client->getConnectionType() == "websocket") + if (client->getConnectionType() == "websocket") { webSocketUserCount--; + } QWriteLocker locker(&clientsLock); clients.removeAt(clientIndex); @@ -245,9 +257,11 @@ void Server::removeClient(Server_ProtocolHandler *client) Event_UserLeft event; event.set_name(data->name()); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); - for (auto &_client : clients) - if (_client->getAcceptsUserListChanges()) + for (auto &_client : clients) { + if (_client->getAcceptsUserListChanges()) { _client->sendProtocolItem(*se); + } + } sendIsl_SessionEvent(*se); delete se; @@ -272,10 +286,11 @@ QList Server::getOnlineModeratorList() const for (auto &client : clients) { ServerInfo_User *data = client->getUserInfo(); - // TODO: this line should be updated in the event there is any type of new user level created + //! \todo This line should be updated in the event there is any type of new user level created. if (data && - (data->user_level() & ServerInfo_User::IsModerator || data->user_level() & ServerInfo_User::IsAdmin)) + (data->user_level() & ServerInfo_User::IsModerator || data->user_level() & ServerInfo_User::IsAdmin)) { results << QString::fromStdString(data->name()).simplified(); + } } return results; } @@ -293,9 +308,11 @@ void Server::externalUserJoined(const ServerInfo_User &userInfo) event.mutable_user_info()->CopyFrom(userInfo); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); - for (auto &client : clients) - if (client->getAcceptsUserListChanges()) + for (auto &client : clients) { + if (client->getAcceptsUserListChanges()) { client->sendProtocolItem(*se); + } + } delete se; clientsLock.unlock(); @@ -319,18 +336,21 @@ void Server::externalUserLeft(const QString &userName) while (userGamesIterator.hasNext()) { userGamesIterator.next(); Server_Room *room = rooms.value(userGamesIterator.value().first); - if (!room) + if (!room) { continue; + } QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(userGamesIterator.key()); - if (!game) + if (!game) { continue; + } QMutexLocker gameLocker(&game->gameMutex); auto *participant = game->getParticipants().value(userGamesIterator.value().second); - if (!participant) + if (!participant) { continue; + } participant->disconnectClient(); } @@ -343,9 +363,11 @@ void Server::externalUserLeft(const QString &userName) SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); clientsLock.lockForRead(); - for (auto &client : clients) - if (client->getAcceptsUserListChanges()) + for (auto &client : clients) { + if (client->getAcceptsUserListChanges()) { client->sendProtocolItem(*se); + } + } clientsLock.unlock(); delete se; } @@ -492,8 +514,9 @@ void Server::externalGameCommandContainerReceived(const CommandContainer &cont, Response::ResponseCode resp = participant->processGameCommand(sc, responseContainer, ges); - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } ges.sendToGame(game); @@ -547,13 +570,16 @@ void Server::broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); clientsLock.lockForRead(); - for (auto &client : clients) - if (client->getAcceptsRoomListChanges()) + for (auto &client : clients) { + if (client->getAcceptsRoomListChanges()) { client->sendProtocolItem(*se); + } + } clientsLock.unlock(); - if (sendToIsl) + if (sendToIsl) { sendIsl_SessionEvent(*se); + } delete se; } @@ -591,8 +617,9 @@ void Server::sendIsl_Response(const Response &item, int serverId, qint64 session { IslMessage msg; msg.set_message_type(IslMessage::RESPONSE); - if (sessionId != -1) + if (sessionId != -1) { msg.set_session_id(static_cast(sessionId)); + } msg.mutable_response()->CopyFrom(item); emit sigSendIslMessage(msg, serverId); @@ -602,8 +629,9 @@ void Server::sendIsl_SessionEvent(const SessionEvent &item, int serverId, qint64 { IslMessage msg; msg.set_message_type(IslMessage::SESSION_EVENT); - if (sessionId != -1) + if (sessionId != -1) { msg.set_session_id(static_cast(sessionId)); + } msg.mutable_session_event()->CopyFrom(item); emit sigSendIslMessage(msg, serverId); @@ -613,8 +641,9 @@ void Server::sendIsl_GameEventContainer(const GameEventContainer &item, int serv { IslMessage msg; msg.set_message_type(IslMessage::GAME_EVENT_CONTAINER); - if (sessionId != -1) + if (sessionId != -1) { msg.set_session_id(static_cast(sessionId)); + } msg.mutable_game_event_container()->CopyFrom(item); emit sigSendIslMessage(msg, serverId); @@ -624,8 +653,9 @@ void Server::sendIsl_RoomEvent(const RoomEvent &item, int serverId, qint64 sessi { IslMessage msg; msg.set_message_type(IslMessage::ROOM_EVENT); - if (sessionId != -1) + if (sessionId != -1) { msg.set_session_id(static_cast(sessionId)); + } msg.mutable_room_event()->CopyFrom(item); emit sigSendIslMessage(msg, serverId); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp index f9b61ab48..641be1eed 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp @@ -45,25 +45,28 @@ void Server_AbstractUserInterface::sendResponseContainer(const ResponseContainer { const QList> &preResponseQueue = responseContainer.getPreResponseQueue(); - for (int i = 0; i < preResponseQueue.size(); ++i) + for (int i = 0; i < preResponseQueue.size(); ++i) { sendProtocolItemByType(preResponseQueue[i].first, *preResponseQueue[i].second); + } if (responseCode != Response::RespNothing) { Response response; response.set_cmd_id(responseContainer.getCmdId()); response.set_response_code(responseCode); ::google::protobuf::Message *responseExtension = responseContainer.getResponseExtension(); - if (responseExtension) + if (responseExtension) { response.GetReflection() ->MutableMessage(&response, responseExtension->GetDescriptor()->FindExtensionByName("ext")) ->CopyFrom(*responseExtension); + } sendProtocolItem(response); } const QList> &postResponseQueue = responseContainer.getPostResponseQueue(); - for (int i = 0; i < postResponseQueue.size(); ++i) + for (int i = 0; i < postResponseQueue.size(); ++i) { sendProtocolItemByType(postResponseQueue[i].first, *postResponseQueue[i].second); + } } void Server_AbstractUserInterface::playerRemovedFromGame(Server_Game *game) @@ -92,18 +95,21 @@ void Server_AbstractUserInterface::joinPersistentGames(ResponseContainer &rc) const PlayerReference &pr = gamesToJoin.at(i); Server_Room *room = server->getRooms().value(pr.getRoomId()); - if (!room) + if (!room) { continue; + } QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(pr.getGameId()); - if (!game) + if (!game) { continue; + } QMutexLocker gameLocker(&game->gameMutex); auto *participant = game->getParticipants().value(pr.getPlayerId()); - if (!participant) + if (!participant) { continue; + } participant->setUserInterface(this); playerAddedToGame(game->getGameId(), room->getId(), participant->getPlayerId()); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h b/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h index 07b2d3d2b..b478a4244 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h @@ -24,7 +24,7 @@ public: { return playerId; } - bool operator==(const PlayerReference &other) + bool operator==(const PlayerReference &other) const { return ((roomId == other.roomId) && (gameId == other.gameId) && (playerId == other.playerId)); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp index bfd8d113c..27ebaf228 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp @@ -48,8 +48,9 @@ Server_ProtocolHandler::~Server_ProtocolHandler() // The thread must not hold any server locks when calling this (e.g. clientsLock, roomsLock). void Server_ProtocolHandler::prepareDestroy() { - if (deleted) + if (deleted) { return; + } deleted = true; for (auto *room : rooms.values()) { @@ -64,8 +65,9 @@ void Server_ProtocolHandler::prepareDestroy() gameIterator.next(); Server_Room *room = server->getRooms().value(gameIterator.value().first); - if (!room) + if (!room) { continue; + } room->gamesLock.lockForRead(); Server_Game *game = room->getGames().value(gameIterator.key()); if (!game) { @@ -167,8 +169,9 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co default: resp = processExtendedSessionCommand(num, sc, rc); } - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } return finalResponseCode; } @@ -176,13 +179,15 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const CommandContainer &cont, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } QReadLocker locker(&server->roomsLock); Server_Room *room = rooms.value(cont.room_id(), 0); - if (!room) + if (!room) { return Response::RespNotInRoom; + } resetIdleTimer(); @@ -206,8 +211,9 @@ Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const resp = cmdJoinGame(sc.GetExtension(Command_JoinGame::ext), room, rc); break; } - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } return finalResponseCode; } @@ -232,18 +238,21 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const // allows a user to sideboard without receiving flooding message << GameCommand::MOVE_CARD; - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } QMap> gameMap = getGames(); - if (!gameMap.contains(cont.game_id())) + if (!gameMap.contains(cont.game_id())) { return Response::RespNotInRoom; + } const QPair roomIdAndPlayerId = gameMap.value(cont.game_id()); QReadLocker roomsLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); - if (!room) + if (!room) { return Response::RespNotInRoom; + } QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); @@ -258,8 +267,9 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const QMutexLocker gameLocker(&game->gameMutex); auto *participant = game->getParticipants().value(roomIdAndPlayerId.second); - if (!participant) + if (!participant) { return Response::RespNotInRoom; + } resetIdleTimer(); @@ -274,11 +284,13 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const if (commandCountingInterval > 0) { int totalCount = 0; - if (commandCountOverTime.isEmpty()) + if (commandCountOverTime.isEmpty()) { commandCountOverTime.prepend(0); + } - if (!antifloodCommandsWhiteList.contains((GameCommand::GameCommandType)getPbExtension(sc))) + if (!antifloodCommandsWhiteList.contains((GameCommand::GameCommandType)getPbExtension(sc))) { ++commandCountOverTime[0]; + } for (int count : commandCountOverTime) { totalCount += count; @@ -291,8 +303,9 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const Response::ResponseCode resp = participant->processGameCommand(sc, rc, ges); - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } ges.sendToGame(game); @@ -302,10 +315,12 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer(const CommandContainer &cont, ResponseContainer &rc) { - if (!userInfo) + if (!userInfo) { return Response::RespLoginNeeded; - if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) + } + if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) { return Response::RespLoginNeeded; + } resetIdleTimer(); @@ -317,8 +332,9 @@ Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer( logDebugMessage(getSafeDebugString(sc)); resp = processExtendedModeratorCommand(num, sc, rc); - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } return finalResponseCode; } @@ -326,10 +342,12 @@ Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer( Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(const CommandContainer &cont, ResponseContainer &rc) { - if (!userInfo) + if (!userInfo) { return Response::RespLoginNeeded; - if (!(userInfo->user_level() & ServerInfo_User::IsAdmin)) + } + if (!(userInfo->user_level() & ServerInfo_User::IsAdmin)) { return Response::RespLoginNeeded; + } resetIdleTimer(); @@ -341,8 +359,9 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons logDebugMessage(getSafeDebugString(sc)); resp = processExtendedAdminCommand(num, sc, rc); - if (resp != Response::RespOk) + if (resp != Response::RespOk) { finalResponseCode = resp; + } } return finalResponseCode; } @@ -350,29 +369,32 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons void Server_ProtocolHandler::processCommandContainer(const CommandContainer &cont) { // Command processing must be disabled after prepareDestroy() has been called. - if (deleted) + if (deleted) { return; + } lastDataReceived = timeRunning; ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1); Response::ResponseCode finalResponseCode; - if (cont.game_command_size()) + if (cont.game_command_size()) { finalResponseCode = processGameCommandContainer(cont, responseContainer); - else if (cont.room_command_size()) + } else if (cont.room_command_size()) { finalResponseCode = processRoomCommandContainer(cont, responseContainer); - else if (cont.session_command_size()) + } else if (cont.session_command_size()) { finalResponseCode = processSessionCommandContainer(cont, responseContainer); - else if (cont.moderator_command_size()) + } else if (cont.moderator_command_size()) { finalResponseCode = processModeratorCommandContainer(cont, responseContainer); - else if (cont.admin_command_size()) + } else if (cont.admin_command_size()) { finalResponseCode = processAdminCommandContainer(cont, responseContainer); - else + } else { finalResponseCode = Response::RespInvalidCommand; + } - if ((finalResponseCode != Response::RespNothing)) + if ((finalResponseCode != Response::RespNothing)) { sendResponseContainer(responseContainer, finalResponseCode); + } } void Server_ProtocolHandler::pingClockTimeout() @@ -386,11 +408,13 @@ void Server_ProtocolHandler::pingClockTimeout() if (interval > 0) { if (pingclockinterval > 0) { messageSizeOverTime.prepend(0); - if (messageSizeOverTime.size() > (msgcountinterval / pingclockinterval)) + if (messageSizeOverTime.size() > (msgcountinterval / pingclockinterval)) { messageSizeOverTime.removeLast(); + } messageCountOverTime.prepend(0); - if (messageCountOverTime.size() > (msgcountinterval / pingclockinterval)) + if (messageCountOverTime.size() > (msgcountinterval / pingclockinterval)) { messageCountOverTime.removeLast(); + } } } @@ -398,13 +422,15 @@ void Server_ProtocolHandler::pingClockTimeout() if (interval > 0) { if (pingclockinterval > 0) { commandCountOverTime.prepend(0); - if (commandCountOverTime.size() > (cmdcountinterval / pingclockinterval)) + if (commandCountOverTime.size() > (cmdcountinterval / pingclockinterval)) { commandCountOverTime.removeLast(); + } } } - if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime()) + if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime()) { prepareDestroy(); + } // PrivLevel users, Moderators, and Admins are not subject to the server idle timeout policy const bool hasPrivLevel = userInfo && QString::fromStdString(userInfo->privlevel()).toLower() != "none"; @@ -444,8 +470,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd QString password; bool needsHash = false; if (cmd.has_password()) { - if (cmd.password().length() > MAX_NAME_LENGTH) + if (cmd.password().length() > MAX_NAME_LENGTH) { return Response::RespWrongPassword; + } password = QString::fromStdString(cmd.password()); needsHash = true; } else if (cmd.hashed_password().length() > MAX_NAME_LENGTH) { @@ -493,8 +520,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd case UserIsBanned: { auto *re = new Response_Login; re->set_denied_reason_str(reasonStr.toStdString()); - if (banSecondsLeft != 0) + if (banSecondsLeft != 0) { re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsLeft).toSecsSinceEpoch()); + } rc.setResponseExtension(re); return Response::RespUserIsBanned; } @@ -539,19 +567,22 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd if (authState == PasswordRight) { QMapIterator buddyIterator(databaseInterface->getBuddyList(userName)); - while (buddyIterator.hasNext()) + while (buddyIterator.hasNext()) { re->add_buddy_list()->CopyFrom(buddyIterator.next().value()); + } QMapIterator ignoreIterator(databaseInterface->getIgnoreList(userName)); - while (ignoreIterator.hasNext()) + while (ignoreIterator.hasNext()) { re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); + } } // return to client any missing features the server has that the client does not if (!missingClientFeatures.isEmpty()) { QMap::iterator i; - for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) + for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) { re->add_missing_features(i.key().toStdString().c_str()); + } } joinPersistentGames(rc); @@ -562,8 +593,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } QReadLocker locker(&server->clientsLock); @@ -599,8 +631,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } // Do not show games to someone on the ignore list of that user, except for mods QString target_user = nameFromStdString(cmd.user_name()); @@ -624,8 +657,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G room->gamesLock.lockForRead(); room->getInfo(*re->add_room_list(), false, true); QListIterator gameIterator(room->getGamesOfUser(nameFromStdString(cmd.user_name()))); - while (gameIterator.hasNext()) + while (gameIterator.hasNext()) { re->add_game_list()->CopyFrom(gameIterator.next()); + } room->gamesLock.unlock(); } server->roomsLock.unlock(); @@ -636,14 +670,15 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } QString userName = nameFromStdString(cmd.user_name()); auto *re = new Response_GetUserInfo; - if (userName.isEmpty()) + if (userName.isEmpty()) { re->mutable_user_info()->CopyFrom(*userInfo); - else { + } else { QReadLocker locker(&server->clientsLock); @@ -662,13 +697,15 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetU Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRooms & /*cmd*/, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } Event_ListRooms event; QMapIterator roomIterator(server->getRooms()); - while (roomIterator.hasNext()) + while (roomIterator.hasNext()) { roomIterator.next().value()->getInfo(*event.add_room_list(), false); + } rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); acceptsRoomListChanges = true; @@ -677,20 +714,25 @@ Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRo Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } - if (rooms.contains(cmd.room_id())) + if (rooms.contains(cmd.room_id())) { return Response::RespContextError; + } QReadLocker serverLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(cmd.room_id(), 0); - if (!room) + if (!room) { return Response::RespNameNotFound; + } - if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) - if (!(room->userMayJoin(*userInfo))) + if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) { + if (!(room->userMayJoin(*userInfo))) { return Response::RespUserLevelTooLow; + } + } room->addClient(this); rooms.insert(room->getId(), room); @@ -722,17 +764,20 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUsers & /*cmd*/, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } auto *re = new Response_ListUsers; server->clientsLock.lockForRead(); QMapIterator userIterator = server->getUsers(); - while (userIterator.hasNext()) + while (userIterator.hasNext()) { re->add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(false)); + } QMapIterator extIterator = server->getExternalUsers(); - while (extIterator.hasNext()) + while (extIterator.hasNext()) { re->add_user_list()->CopyFrom(extIterator.next().value()->copyUserInfo(false)); + } acceptsUserListChanges = true; server->clientsLock.unlock(); @@ -799,10 +844,12 @@ Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room Response::ResponseCode Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; - if (cmd.password().length() > MAX_NAME_LENGTH) + } + if (cmd.password().length() > MAX_NAME_LENGTH) { return Response::RespContextError; + } auto level = userInfo->user_level(); bool isJudge = level & ServerInfo_User::IsJudge; @@ -852,8 +899,9 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room Response::ResponseCode Server_ProtocolHandler::cmdJoinGame(const Command_JoinGame &cmd, Server_Room *room, ResponseContainer &rc) { - if (authState == NotLoggedIn) + if (authState == NotLoggedIn) { return Response::RespLoginNeeded; + } return room->processJoinGameCommand(cmd, rc, this); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_response_containers.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_response_containers.cpp index 9b07bdb91..22fb199fb 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_response_containers.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_response_containers.cpp @@ -25,8 +25,9 @@ GameEventStorage::GameEventStorage() : gameEventContext(0), privatePlayerId(0) GameEventStorage::~GameEventStorage() { delete gameEventContext; - for (int i = 0; i < gameEventList.size(); ++i) + for (int i = 0; i < gameEventList.size(); ++i) { delete gameEventList[i]; + } } void GameEventStorage::setGameEventContext(const ::google::protobuf::Message &_gameEventContext) @@ -44,14 +45,16 @@ void GameEventStorage::enqueueGameEvent(const ::google::protobuf::Message &event int _privatePlayerId) { gameEventList.append(new GameEventStorageItem(event, playerId, recipients)); - if (_privatePlayerId != -1) + if (_privatePlayerId != -1) { privatePlayerId = _privatePlayerId; + } } void GameEventStorage::sendToGame(Server_Game *game) { - if (gameEventList.isEmpty()) + if (gameEventList.isEmpty()) { return; + } auto *contPrivate = new GameEventContainer; auto *contOthers = new GameEventContainer; @@ -68,10 +71,12 @@ void GameEventStorage::sendToGame(Server_Game *game) for (const auto &i : gameEventList) { const GameEvent &event = i->getGameEvent(); const GameEventStorageItem::EventRecipients recipients = i->getRecipients(); - if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) + if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { contPrivate->add_event_list()->CopyFrom(event); - if (recipients.testFlag(GameEventStorageItem::SendToOthers)) + } + if (recipients.testFlag(GameEventStorageItem::SendToOthers)) { contOthers->add_event_list()->CopyFrom(event); + } } if (gameEventContext) { contPrivate->mutable_context()->CopyFrom(*gameEventContext); @@ -88,8 +93,10 @@ ResponseContainer::ResponseContainer(int _cmdId) : cmdId(_cmdId), responseExtens ResponseContainer::~ResponseContainer() { delete responseExtension; - for (int i = 0; i < preResponseQueue.size(); ++i) + for (int i = 0; i < preResponseQueue.size(); ++i) { delete preResponseQueue[i].second; - for (int i = 0; i < postResponseQueue.size(); ++i) + } + for (int i = 0; i < postResponseQueue.size(); ++i) { delete postResponseQueue[i].second; + } } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp index bfa8912b1..1bd928e09 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp @@ -42,8 +42,9 @@ Server_Room::~Server_Room() gamesLock.lockForWrite(); const QList gameList = games.values(); - for (int i = 0; i < gameList.size(); ++i) + for (int i = 0; i < gameList.size(); ++i) { delete gameList[i]; + } games.clear(); gamesLock.unlock(); @@ -55,19 +56,23 @@ Server_Room::~Server_Room() bool Server_Room::userMayJoin(const ServerInfo_User &userInfo) { - if (permissionLevel.toLower() == "administrator" || permissionLevel.toLower() == "moderator") + if (permissionLevel.toLower() == "administrator" || permissionLevel.toLower() == "moderator") { return false; + } - if (permissionLevel.toLower() == "registered" && !(userInfo.user_level() & ServerInfo_User::IsRegistered)) + if (permissionLevel.toLower() == "registered" && !(userInfo.user_level() & ServerInfo_User::IsRegistered)) { return false; + } if (privilegeLevel.toLower() != "none") { if (privilegeLevel.toLower() == "privileged") { - if (privilegeLevel.toLower() == "none") + if (privilegeLevel.toLower() == "none") { return false; + } } else { - if (privilegeLevel.toLower() != QString::fromStdString(userInfo.privlevel()).toLower()) + if (privilegeLevel.toLower() != QString::fromStdString(userInfo.privlevel()).toLower()) { return false; + } } } return true; @@ -92,12 +97,14 @@ Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes, result.set_game_count(games.size() + externalGames.size()); if (complete) { QMapIterator gameIterator(games); - while (gameIterator.hasNext()) + while (gameIterator.hasNext()) { gameIterator.next().value()->getInfo(*result.add_game_list()); + } if (includeExternalData) { QMapIterator externalGameIterator(externalGames); - while (externalGameIterator.hasNext()) + while (externalGameIterator.hasNext()) { result.add_game_list()->CopyFrom(externalGameIterator.next().value()); + } } } gamesLock.unlock(); @@ -106,22 +113,25 @@ Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes, result.set_player_count(users.size() + externalUsers.size()); if (complete) { QMapIterator userIterator(users); - while (userIterator.hasNext()) + while (userIterator.hasNext()) { result.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(false)); + } if (includeExternalData) { QMapIterator externalUserIterator(externalUsers); - while (externalUserIterator.hasNext()) + while (externalUserIterator.hasNext()) { result.add_user_list()->CopyFrom(externalUserIterator.next().value().copyUserInfo(false)); + } } } usersLock.unlock(); - if (complete || showGameTypes) + if (complete || showGameTypes) { for (int i = 0; i < gameTypes.size(); ++i) { ServerInfo_GameType *gameTypeInfo = result.add_gametype_list(); gameTypeInfo->set_game_type_id(i); gameTypeInfo->set_description(gameTypes[i].toStdString()); } + } return result; } @@ -208,8 +218,9 @@ void Server_Room::removeExternalUser(const QString &_name) roomInfo.set_room_id(id); usersLock.lockForWrite(); - if (externalUsers.contains(_name)) + if (externalUsers.contains(_name)) { externalUsers.remove(_name); + } roomInfo.set_player_count(users.size() + externalUsers.size()); usersLock.unlock(); @@ -227,10 +238,11 @@ void Server_Room::updateExternalGameList(const ServerInfo_Game &gameInfo) roomInfo.set_room_id(id); gamesLock.lockForWrite(); - if (!gameInfo.has_player_count() && externalGames.contains(gameInfo.game_id())) + if (!gameInfo.has_player_count() && externalGames.contains(gameInfo.game_id())) { externalGames.remove(gameInfo.game_id()); - else + } else { externalGames.insert(gameInfo.game_id(), gameInfo); + } roomInfo.set_game_count(games.size() + externalGames.size()); gamesLock.unlock(); @@ -242,8 +254,9 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam ResponseContainer &rc, Server_AbstractUserInterface *userInterface) { - if (cmd.password().length() > MAX_NAME_LENGTH) + if (cmd.password().length() > MAX_NAME_LENGTH) { return Response::RespWrongPassword; + } // This function is called from the Server thread and from the S_PH thread. // server->roomsMutex is always locked. @@ -271,8 +284,9 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam Response::ResponseCode result = game->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(), cmd.override_restrictions(), cmd.join_as_judge()); - if (result == Response::RespOk) + if (result == Response::RespOk) { game->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge()); + } return result; } @@ -329,13 +343,15 @@ void Server_Room::sendRoomEvent(RoomEvent *event, bool sendToIsl) usersLock.lockForRead(); { QMapIterator userIterator(users); - while (userIterator.hasNext()) + while (userIterator.hasNext()) { userIterator.next().value()->sendProtocolItem(*event); + } } usersLock.unlock(); - if (sendToIsl) + if (sendToIsl) { static_cast(parent())->sendIsl_RoomEvent(*event); + } delete event; } @@ -405,9 +421,11 @@ int Server_Room::getGamesCreatedByUser(const QString &userName) const QMapIterator gamesIterator(games); int result = 0; - while (gamesIterator.hasNext()) - if (gamesIterator.next().value()->getCreatorInfo()->name() == userName.toStdString()) + while (gamesIterator.hasNext()) { + if (gamesIterator.next().value()->getCreatorInfo()->name() == userName.toStdString()) { ++result; + } + } return result; } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/serverinfo_user_container.cpp b/libcockatrice_network/libcockatrice/network/server/remote/serverinfo_user_container.cpp index 77ff38906..24e734a4e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/serverinfo_user_container.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/serverinfo_user_container.cpp @@ -13,10 +13,11 @@ ServerInfo_User_Container::ServerInfo_User_Container(const ServerInfo_User &_use ServerInfo_User_Container::ServerInfo_User_Container(const ServerInfo_User_Container &other) { - if (other.userInfo) + if (other.userInfo) { userInfo = new ServerInfo_User(*other.userInfo); - else + } else { userInfo = nullptr; + } } ServerInfo_User_Container::~ServerInfo_User_Container() @@ -45,8 +46,9 @@ ServerInfo_User &ServerInfo_User_Container::copyUserInfo(ServerInfo_User &result result.clear_id(); result.clear_email(); } - if (!complete) + if (!complete) { result.clear_avatar_bmp(); + } } return result; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp index 718487c18..c419a68d4 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp @@ -58,8 +58,9 @@ void SafePrinter::applySafePrinter(const ::google::protobuf::Message &message, case ::google::protobuf::FieldDescriptor::CPPTYPE_STRING: if (field->name().find("password") != std::string::npos) { // name contains password auto *safePrinter = new SafePrinter(); - if (!printer.RegisterFieldValuePrinter(field, safePrinter)) + if (!printer.RegisterFieldValuePrinter(field, safePrinter)) { delete safePrinter; // in case safePrinter has not been taken ownership of + } } break; case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp index 1b08c4040..3e687ef56 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp @@ -33,14 +33,16 @@ void FeatureSet::initalizeFeatureList(QMap &_featureList) void FeatureSet::enableRequiredFeature(QMap &_featureList, const QString &featureName) { - if (_featureList.contains(featureName)) + if (_featureList.contains(featureName)) { _featureList.insert(featureName, true); + } } void FeatureSet::disableRequiredFeature(QMap &_featureList, const QString &featureName) { - if (_featureList.contains(featureName)) + if (_featureList.contains(featureName)) { _featureList.insert(featureName, false); + } } QMap diff --git a/libcockatrice_protocol/libcockatrice/protocol/get_pb_extension.cpp b/libcockatrice_protocol/libcockatrice/protocol/get_pb_extension.cpp index d6235858a..a693ea5a7 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/get_pb_extension.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/get_pb_extension.cpp @@ -7,8 +7,10 @@ int getPbExtension(const ::google::protobuf::Message &message) { std::vector fieldList; message.GetReflection()->ListFields(message, &fieldList); - for (unsigned int j = 0; j < fieldList.size(); ++j) - if (fieldList[j]->is_extension()) + for (unsigned int j = 0; j < fieldList.size(); ++j) { + if (fieldList[j]->is_extension()) { return fieldList[j]->number(); + } + } return -1; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 212ab69dd..b4c7b6ac8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -76,6 +76,7 @@ set(PROTO_FILES event_game_closed.proto event_game_host_changed.proto event_game_joined.proto + event_game_log_notice.proto event_game_say.proto event_game_state_changed.proto event_game_state_changed.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_log_notice.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_log_notice.proto new file mode 100644 index 000000000..ef0dcc102 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_game_log_notice.proto @@ -0,0 +1,20 @@ +syntax = "proto2"; +import "game_event.proto"; + +// Notifies clients of an event that happened, and which could safely be dropped without affect the game state. +// This mostly just means events that should cause a message to be logged to chat. +message Event_GameLogNotice { + + // The type of the notice. + // Clients who do not recognize the type should drop the event. + enum NoticeType { + // Player's "undo draw" command failed due to losing track of recent draw + UNDO_DRAW_FAILED = 1; + } + + extend GameEvent { + optional Event_GameLogNotice ext = 2022; + } + + optional NoticeType notice_type = 1; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto index 8682128af..7d3147701 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/game_event.proto @@ -33,6 +33,7 @@ message GameEvent { // STOP_DUMP_ZONE = 2019; // obsolete CHANGE_ZONE_PROPERTIES = 2020; REVERSE_TURN = 2021; + GAME_LOG_NOTICE = 2022; } optional sint32 player_id = 1 [default = -1]; extensions 100 to max; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pending_command.h b/libcockatrice_protocol/libcockatrice/protocol/pending_command.h index 1d2d9ff17..dbe57e7fc 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pending_command.h +++ b/libcockatrice_protocol/libcockatrice/protocol/pending_command.h @@ -1,8 +1,8 @@ /** * @file pending_command.h * @ingroup Messages - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef PENDING_COMMAND_H #define PENDING_COMMAND_H diff --git a/libcockatrice_rng/libcockatrice/rng/rng_abstract.cpp b/libcockatrice_rng/libcockatrice/rng/rng_abstract.cpp index 63072b988..82404d351 100644 --- a/libcockatrice_rng/libcockatrice/rng/rng_abstract.cpp +++ b/libcockatrice_rng/libcockatrice/rng/rng_abstract.cpp @@ -8,10 +8,11 @@ QVector RNG_Abstract::makeNumbersVector(int n, int min, int max) QVector result(bins); for (int i = 0; i < n; ++i) { int number = rand(min, max); - if ((number < min) || (number > max)) + if ((number < min) || (number > max)) { qDebug() << "rand(" << min << "," << max << ") returned " << number; - else + } else { result[number - min]++; + } } return result; } @@ -19,12 +20,14 @@ QVector RNG_Abstract::makeNumbersVector(int n, int min, int max) double RNG_Abstract::testRandom(const QVector &numbers) const { int n = 0; - for (int i = 0; i < numbers.size(); ++i) + for (int i = 0; i < numbers.size(); ++i) { n += numbers[i]; + } double expected = (double)n / (double)numbers.size(); double chisq = 0; - for (int i = 0; i < numbers.size(); ++i) + for (int i = 0; i < numbers.size(); ++i) { chisq += ((double)numbers[i] - expected) * ((double)numbers[i] - expected) / expected; + } return chisq; } diff --git a/libcockatrice_rng/libcockatrice/rng/rng_sfmt.cpp b/libcockatrice_rng/libcockatrice/rng/rng_sfmt.cpp index 5a6d8c862..5b38deb3f 100644 --- a/libcockatrice_rng/libcockatrice/rng/rng_sfmt.cpp +++ b/libcockatrice_rng/libcockatrice/rng/rng_sfmt.cpp @@ -40,8 +40,9 @@ unsigned int RNG_SFMT::rand(int min, int max) } // For complete fairness and equal timing, this should be a roll, but let's skip it anyway - if (min == max) + if (min == max) { return max; + } // This is actually not used in Cockatrice: // Someone wants rand() % -foo, so we should compute -rand(0, +foo) diff --git a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.c b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.c index b4ac9308b..fde6367a0 100644 --- a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.c +++ b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.c @@ -60,7 +60,9 @@ inline static void swap(w128_t *array, int size); */ static const w128_t sse2_param_mask = {{SFMT_MSK1, SFMT_MSK2, SFMT_MSK3, SFMT_MSK4}}; - #if defined(_MSC_VER) + #if defined(__AVX2__) && (SFMT_SL1 >= 16) && !(SFMT_N & 1) && !(SFMT_POS1 & 1) + #include "SFMT-avx256.h" + #elif defined(_MSC_VER) #include "SFMT-sse2-msc.h" #else #include "SFMT-sse2.h" diff --git a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h index 79e012d63..34d9e746f 100644 --- a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h +++ b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h @@ -88,8 +88,13 @@ union W128_T { uint64_t u64[2]; uint32x4_t si; }; +//#elif defined(HAVE_SSE2) #elif defined(HAVE_SSE2) - #include + #if defined(__AVX2__) + #include + #else + #include + #endif /** 128-bit data structure */ union W128_T { @@ -112,8 +117,18 @@ typedef union W128_T w128_t; * SFMT internal state */ struct SFMT_T { +#if defined(__AVX2__) + union { + w128_t state[SFMT_N]; + __m256i state_ymm[SFMT_N/2]; + #if defined(__AVX512VL__) + __m512i state_zmm[SFMT_N/4]; + #endif + }; +#else /** the 128-bit internal state array */ w128_t state[SFMT_N]; +#endif /** index counter to the 32-bit internal state array */ int idx; }; @@ -249,9 +264,9 @@ inline static double sfmt_genrand_real3(sfmt_t * sfmt) } /** - * converts an unsigned 32-bit integer to double on [0,1) + * converts an unsigned 64-bit integer to double on [0,1) * with 53-bit resolution. - * @param v 32-bit unsigned integer + * @param v 64-bit unsigned integer * @return double on [0,1)-real-interval with 53-bit resolution. */ inline static double sfmt_to_res53(uint64_t v) diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp index 26a91a4dd..4887afd2f 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp @@ -34,3 +34,17 @@ bool CardDatabaseSettings::isKnown(QString shortName) const { return getValue("isknown", "sets", std::move(shortName)).toBool(); } + +void CardDatabaseSettings::saveSets(const QVector &data) +{ + batchWrite([&](QSettings &s) { + s.beginGroup("sets"); + for (const auto &entry : data) { + s.beginGroup(entry.shortName); + s.setValue("sortkey", entry.sortKey); + s.setValue("enabled", entry.enabled); + s.endGroup(); + } + s.endGroup(); + }); +} \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h index bb946ea80..97efb5753 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h @@ -2,8 +2,8 @@ * @file card_database_settings.h * @ingroup CardDatabase * @ingroup CardSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef CARDDATABASESETTINGS_H #define CARDDATABASESETTINGS_H @@ -26,6 +26,8 @@ public: bool isEnabled(QString shortName) const override; bool isKnown(QString shortName) const override; + void saveSets(const QVector &data) override; + private: explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr); }; diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h index 3d9db4e65..cd515d4da 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h @@ -1,8 +1,8 @@ /** * @file card_override_settings.h * @ingroup CardSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_CARD_OVERRIDE_SETTINGS_H #define COCKATRICE_CARD_OVERRIDE_SETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.h b/libcockatrice_settings/libcockatrice/settings/debug_settings.h index 30cdd5fa5..acb5cf313 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.h @@ -1,8 +1,8 @@ /** * @file debug_settings.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef DEBUG_SETTINGS_H #define DEBUG_SETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index b7442301e..60e59220b 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -1,8 +1,8 @@ /** * @file download_settings.h * @ingroup NetworkSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef COCKATRICE_DOWNLOADSETTINGS_H #define COCKATRICE_DOWNLOADSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index c0e60551a..24f582007 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -2,8 +2,8 @@ * @file game_filters_settings.h * @ingroup Lobby * @ingroup GameSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef GAMEFILTERSSETTINGS_H #define GAMEFILTERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index 5353ce15a..d5f26b61b 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -1,8 +1,8 @@ /** * @file layouts_settings.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LAYOUTSSETTINGS_H #define LAYOUTSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.h b/libcockatrice_settings/libcockatrice/settings/message_settings.h index ec70027af..8276aa39d 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.h @@ -1,8 +1,8 @@ /** * @file message_settings.h * @ingroup NetworkSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef MESSAGESETTINGS_H #define MESSAGESETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.h b/libcockatrice_settings/libcockatrice/settings/recents_settings.h index 3aebff334..01b2a37bc 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.h @@ -1,8 +1,8 @@ /** * @file recents_settings.h * @ingroup DeckSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef RECENTS_SETTINGS_H #define RECENTS_SETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp index 0140182be..d9b98e036 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp @@ -58,9 +58,11 @@ int ServersSettings::getPrevioushostindex(const QString &saveName) const { int size = getValue("totalServers", "server", "server_details").toInt(); - for (int i = 0; i <= size; ++i) - if (saveName == getValue(QString("saveName%1").arg(i), "server", "server_details").toString()) + for (int i = 0; i <= size; ++i) { + if (saveName == getValue(QString("saveName%1").arg(i), "server", "server_details").toString()) { return i; + } + } return -1; } @@ -92,8 +94,9 @@ QString ServersSettings::getPassword() { int index = getPrevioushostindex(getPrevioushostName()); - if (getSavePassword()) + if (getSavePassword()) { return getValue(QString("password%1").arg(index), "server", "server_details").toString(); + } return QString(); } @@ -168,8 +171,9 @@ void ServersSettings::addNewServer(const QString &saveName, bool savePassword, const QString &site) { - if (updateExistingServer(saveName, serv, port, username, password, savePassword, site)) + if (updateExistingServer(saveName, serv, port, username, password, savePassword, site)) { return; + } int index = getValue("totalServers", "server", "server_details").toInt() + 1; diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.h b/libcockatrice_settings/libcockatrice/settings/servers_settings.h index 22603a356..40fa996fb 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.h @@ -1,8 +1,8 @@ /** * @file servers_settings.h * @ingroup NetworkSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SERVERSSETTINGS_H #define SERVERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index 2d4f1c441..b66edd630 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -10,6 +10,7 @@ SettingsManager::SettingsManager(const QString &_settingPath, QSettings SettingsManager::getSettings() const { + // Do not store the QSettings instance in a field, as that is not threadsafe (see #6747) return QSettings(settingPath, QSettings::IniFormat); } @@ -158,6 +159,15 @@ QVariant SettingsManager::getValue(const QString &name, const QString &group, co return value; } +void SettingsManager::batchWrite(std::function batchWriteFunction) +{ + auto settings = getSettings(); + settings.setAtomicSyncRequired(false); + batchWriteFunction(settings); + settings.sync(); // single flush + settings.setAtomicSyncRequired(true); +} + /** * Calls sync on the underlying QSettings object */ @@ -166,4 +176,4 @@ void SettingsManager::sync() auto settings = getSettings(); settings.sync(); -} \ No newline at end of file +} diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index ad828f089..4213cf4c1 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -1,8 +1,8 @@ /** * @file settings_manager.h * @ingroup Settings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H @@ -14,13 +14,17 @@ class SettingsManager : public QObject { Q_OBJECT + public: explicit SettingsManager(const QString &settingPath, const QString &defaultGroup = QString(), const QString &defaultSubGroup = QString(), QObject *parent = nullptr); + QVariant getValue(const QString &name) const; QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()) const; + void batchWrite(std::function batchWriteFunction); + void sync(); protected: @@ -31,9 +35,12 @@ protected: QSettings getSettings() const; void setValue(const QVariant &value, const QString &name); + void setValue(const QVariant &value, const QString &name, const QString &group, const QString &subGroup = QString()); + void deleteValue(const QString &name); + void deleteValue(const QString &name, const QString &group, const QString &subGroup = QString()); }; diff --git a/libcockatrice_utility/libcockatrice/utility/color.h b/libcockatrice_utility/libcockatrice/utility/color.h index f02df3a0e..6afe984bd 100644 --- a/libcockatrice_utility/libcockatrice/utility/color.h +++ b/libcockatrice_utility/libcockatrice/utility/color.h @@ -22,6 +22,7 @@ inline color convertQColorToColor(const QColor &c) return result; } +#include #include namespace GameSpecificColors @@ -45,11 +46,13 @@ inline QColor colorHelper(const QString &name) {"Land", QColor(110, 80, 50)}, }; - if (colorMap.contains(name)) + if (colorMap.contains(name)) { return colorMap[name]; + } - if (name.length() == 1 && colorMap.contains(name.toUpper())) + if (name.length() == 1 && colorMap.contains(name.toUpper())) { return colorMap[name.toUpper()]; + } uint h = qHash(name); int r = 100 + (h % 120); diff --git a/libcockatrice_utility/libcockatrice/utility/days_years_between.h b/libcockatrice_utility/libcockatrice/utility/days_years_between.h new file mode 100644 index 000000000..c0f5da23a --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/days_years_between.h @@ -0,0 +1,8 @@ +#include + +inline static QPair getDaysAndYearsBetween(const QDate &then, const QDate &now) +{ + int years = now.addDays(1 - then.dayOfYear()).year() - then.year(); // there is no yearsTo + int days = then.addYears(years).daysTo(now); + return {days, years}; +} diff --git a/libcockatrice_utility/libcockatrice/utility/expression.cpp b/libcockatrice_utility/libcockatrice/utility/expression.cpp index 42073670c..718b0fe18 100644 --- a/libcockatrice_utility/libcockatrice/utility/expression.cpp +++ b/libcockatrice_utility/libcockatrice/utility/expression.cpp @@ -54,8 +54,9 @@ double Expression::eval(const peg::Ast &ast) return stod(std::string(ast.token)); } else if (ast.name == "FUNCTION") { QString name = QString::fromStdString(std::string(nodes[0]->token)); - if (!fns.contains(name)) + if (!fns.contains(name)) { return 0; + } return fns[name](eval(*nodes[1])); } else if (ast.name == "VARIABLE") { return value; diff --git a/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp b/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp index cfb972f91..f39d325fb 100644 --- a/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp +++ b/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp @@ -9,10 +9,12 @@ int levenshteinDistance(const QString &s1, const QString &s2) int len2 = s2.size(); std::vector> dp(len1 + 1, std::vector(len2 + 1)); - for (int i = 0; i <= len1; i++) + for (int i = 0; i <= len1; i++) { dp[i][0] = i; - for (int j = 0; j <= len2; j++) + } + for (int j = 0; j <= len2; j++) { dp[0][j] = j; + } for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { diff --git a/libcockatrice_utility/libcockatrice/utility/levenshtein.h b/libcockatrice_utility/libcockatrice/utility/levenshtein.h index e83235470..3da730e53 100644 --- a/libcockatrice_utility/libcockatrice/utility/levenshtein.h +++ b/libcockatrice_utility/libcockatrice/utility/levenshtein.h @@ -1,8 +1,8 @@ /** * @file levenshtein.h * @ingroup Core - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef LEVENSHTEIN_H #define LEVENSHTEIN_H diff --git a/libcockatrice_utility/libcockatrice/utility/peglib.h b/libcockatrice_utility/libcockatrice/utility/peglib.h index 3ae6040c4..e7e558dff 100644 --- a/libcockatrice_utility/libcockatrice/utility/peglib.h +++ b/libcockatrice_utility/libcockatrice/utility/peglib.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #if !defined(__cplusplus) || __cplusplus < 201703L @@ -505,7 +506,7 @@ inline constexpr unsigned int str2tag(std::string_view sv) { namespace udl { -inline constexpr unsigned int operator""_(const char *s, size_t l) { +inline constexpr unsigned int operator"" _(const char *s, size_t l) { return str2tag_core(s, l, 0); } @@ -2434,10 +2435,11 @@ struct ComputeFirstSet : public TraversalVisitor { void visit(Sequence &ope) override { for (const auto &op : ope.opes_) { + FirstSet element_fs; auto save = result_; result_ = FirstSet{}; op->accept(*this); - auto element_fs = result_; + element_fs = result_; result_ = save; result_.chars |= element_fs.chars; if (element_fs.any_char) { result_.any_char = true; } @@ -2526,10 +2528,22 @@ struct ComputeFirstSet : public TraversalVisitor { void visit(BackReference &) override { result_.any_char = true; } void visit(Cut &) override { result_.can_be_empty = true; } + // Per-rule cache shared across a SetupFirstSets traversal. Without it, + // every alternative of every PrioritizedChoice re-walks referenced + // rules — O(refs^depth) work for grammars with many cross-references. + // Only cycle-free rule computations are cached; results computed under + // a cycle (left recursion) would be incomplete and unsafe to reuse from + // a different call context. + using FirstSetCache = std::unordered_map; + + explicit ComputeFirstSet(FirstSetCache &cache) : cache_(cache) {} + FirstSet result_; private: - std::unordered_set refs_; + FirstSetCache &cache_; + std::unordered_set refs_; + size_t cycle_count_ = 0; }; struct SetupFirstSets : public TraversalVisitor { @@ -2542,7 +2556,7 @@ struct SetupFirstSets : public TraversalVisitor { ope.first_sets_.clear(); ope.first_sets_.reserve(ope.opes_.size()); for (const auto &op : ope.opes_) { - ComputeFirstSet cfs; + ComputeFirstSet cfs(first_set_cache_); op->accept(cfs); ope.first_sets_.push_back(cfs.result_); } @@ -2559,7 +2573,8 @@ struct SetupFirstSets : public TraversalVisitor { void visit(Reference &ope) override; private: - std::unordered_set refs_; + ComputeFirstSet::FirstSetCache first_set_cache_; + std::unordered_set visited_rules_; }; /* @@ -3806,20 +3821,50 @@ inline void ComputeFirstSet::visit(Reference &ope) { result_.any_char = true; return; } - if (refs_.count(ope.name_)) { return; } - refs_.insert(ope.name_); - ope.rule_->accept(*this); - if (!result_.first_rule && ope.rule_->is_token()) { - result_.first_rule = ope.rule_; + + auto it = cache_.find(ope.rule_); + FirstSet computed; + const FirstSet *rule_fs; + if (it != cache_.end()) { + rule_fs = &it->second; + } else { + if (!refs_.insert(ope.rule_).second) { + cycle_count_++; // cycle / left recursion + return; + } + auto save = std::exchange(result_, FirstSet{}); + auto saved_cycle_count = cycle_count_; + ope.rule_->accept(*this); + computed = std::move(result_); + result_ = std::move(save); + refs_.erase(ope.rule_); + if (cycle_count_ == saved_cycle_count) { + // Cycle-free: cached value is complete and safe to reuse. + it = cache_.try_emplace(ope.rule_, std::move(computed)).first; + rule_fs = &it->second; + } else { + // Cycle was hit during this rule's computation — its result may be + // missing contributions from rules that were on the call stack. + // Use the value here but do not cache it for other call contexts. + rule_fs = &computed; + } + } + + result_.merge(*rule_fs); + if (!result_.first_literal) { + result_.first_literal = rule_fs->first_literal; + } + if (!result_.first_rule) { + result_.first_rule = rule_fs->first_rule + ? rule_fs->first_rule + : (ope.rule_->is_token() ? ope.rule_ : nullptr); } - refs_.erase(ope.name_); } inline void SetupFirstSets::visit(Reference &ope) { - if (!ope.rule_ || refs_.count(ope.name_)) { return; } - refs_.insert(ope.name_); + if (!ope.rule_) { return; } + if (!visited_rules_.insert(ope.rule_).second) { return; } ope.rule_->accept(*this); - refs_.erase(ope.name_); } inline void SetupFirstSets::visit(Sequence &ope) { diff --git a/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h index 606947143..334e56027 100644 --- a/libcockatrice_utility/libcockatrice/utility/qt_utils.h +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -18,14 +18,17 @@ template T *findParentOfType(const QObject *obj) static inline void clearLayoutRec(QLayout *l) { - if (!l) + if (!l) { return; + } QLayoutItem *it; while ((it = l->takeAt(0)) != nullptr) { - if (QWidget *w = it->widget()) + if (QWidget *w = it->widget()) { w->deleteLater(); - if (QLayout *sub = it->layout()) + } + if (QLayout *sub = it->layout()) { clearLayoutRec(sub); + } delete it; } } diff --git a/libcockatrice_utility/libcockatrice/utility/trice_limits.h b/libcockatrice_utility/libcockatrice/utility/trice_limits.h index fa7ce7489..833ce1b98 100644 --- a/libcockatrice_utility/libcockatrice/utility/trice_limits.h +++ b/libcockatrice_utility/libcockatrice/utility/trice_limits.h @@ -15,6 +15,12 @@ constexpr uint MAXIMUM_DIE_SIDES = 1000000; constexpr uint MINIMUM_DICE_TO_ROLL = 1; constexpr uint MAXIMUM_DICE_TO_ROLL = 100; +// Card counter value bounds [0, MAX_COUNTERS_ON_CARD]. +// Counters on cards (e.g., +1/+1 counters, charge counters) are non-negative physical game objects. +// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit. +// Server enforces these bounds; client may also check for UX optimization. +constexpr int MAX_COUNTERS_ON_CARD = 999; + // optimized functions to get qstrings that are at most that long static inline QString nameFromStdString(const std::string &_string) { diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 3bb4de5df..a51982625 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -28,6 +28,7 @@ set(oracle_SOURCES ../cockatrice/src/client/settings/card_counter_settings.cpp ../cockatrice/src/client/settings/shortcuts_settings.cpp ../cockatrice/src/client/network/update/client/release_channel.cpp + ../cockatrice/src/interface/theme_config.cpp ../cockatrice/src/interface/theme_manager.cpp ../cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp ../cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index b5d7b9856..bace63508 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -124,10 +124,11 @@ static void sortAndReduceColors(QString &colors) // reduce QChar lastChar = '\0'; for (int i = 0; i < colors.size(); ++i) { - if (colors.at(i) == lastChar) + if (colors.at(i) == lastChar) { colors.remove(i, 1); - else + } else { lastChar = colors.at(i); + } } } @@ -191,12 +192,13 @@ CardInfoPtr OracleImporter::addCard(QString name, // table row int tableRow = 1; QString mainCardType = properties.value("maintype").toString(); - if (mainCardType == "Land") + if (mainCardType == "Land") { tableRow = 0; - else if (mainCardType == "Sorcery" || mainCardType == "Instant") + } else if (mainCardType == "Sorcery" || mainCardType == "Instant") { tableRow = 3; - else if (mainCardType == "Creature") + } else if (mainCardType == "Creature") { tableRow = 2; + } // card side QString side = properties.value("side").toString() == "b" ? "back" : "front"; @@ -282,8 +284,9 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList QString mtgjsonProperty = i.key(); QString xmlPropertyName = i.value(); QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty); - if (!propertyValue.isEmpty()) + if (!propertyValue.isEmpty()) { properties.insert(xmlPropertyName, propertyValue); + } } // per-set properties @@ -292,8 +295,9 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList QString mtgjsonProperty = i.key(); QString xmlPropertyName = i.value(); QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty); - if (!propertyValue.isEmpty()) + if (!propertyValue.isEmpty()) { printingInfo.setProperty(xmlPropertyName, propertyValue); + } } // handle flavorNames specially due to double-faced cards @@ -366,8 +370,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList auto found_iter = splitCards.find(name + numProperty); if (found_iter == splitCards.end()) { splitCards.insert(name + numProperty, {{split}, name}); - } else if (layout == "adventure" || layout == "prepare") { - found_iter->first.insert(0, split); } else { found_iter->first.append(split); } @@ -471,8 +473,8 @@ FormatRulesNameMap OracleImporter::createDefaultMagicFormats() // Predefined common exceptions CardCondition superTypeIsBasic; superTypeIsBasic.field = "type"; - superTypeIsBasic.matchType = "contains"; - superTypeIsBasic.value = "Basic Land"; + superTypeIsBasic.matchType = "regex"; + superTypeIsBasic.value = "\bBasic\b[^—]+\bLand\b"; ExceptionRule basicLands; basicLands.conditions.append(superTypeIsBasic); @@ -546,8 +548,9 @@ int OracleImporter::startImport() CardSetPtr newSet = CardSet::newInstance(noOpController, curSetToParse.getShortName(), curSetToParse.getLongName(), curSetToParse.getSetType(), curSetToParse.getReleaseDate(), curSetToParse.getPriority()); - if (!sets.contains(newSet->getShortName())) + if (!sets.contains(newSet->getShortName())) { sets.insert(newSet->getShortName(), newSet); + } int numCardsInSet = importCardsFromSet(newSet, curSetToParse.getCards()); diff --git a/oracle/src/pagetemplates.h b/oracle/src/pagetemplates.h index 79dcdd632..6e79c867e 100644 --- a/oracle/src/pagetemplates.h +++ b/oracle/src/pagetemplates.h @@ -27,7 +27,7 @@ protected: inline OracleWizard *wizard() { return (OracleWizard *)QWizardPage::wizard(); - }; + } }; class SimpleDownloadFilePage : public OracleWizardPage diff --git a/oracle/translations/oracle_de.ts b/oracle/translations/oracle_de.ts index bdf344048..df12f40f4 100644 --- a/oracle/translations/oracle_de.ts +++ b/oracle/translations/oracle_de.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Platzhalter Edition mit Spielsteinen @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer diff --git a/oracle/translations/oracle_el.ts b/oracle/translations/oracle_el.ts index 73260a16d..7934997cf 100644 --- a/oracle/translations/oracle_el.ts +++ b/oracle/translations/oracle_el.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Εικονικό σετ που περιέχει tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Εισαγωγέας Oracle diff --git a/oracle/translations/oracle_en_US.ts b/oracle/translations/oracle_en_US.ts index 348b26323..4b664bf57 100644 --- a/oracle/translations/oracle_en_US.ts +++ b/oracle/translations/oracle_en_US.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dummy set containing tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer diff --git a/oracle/translations/oracle_es.ts b/oracle/translations/oracle_es.ts index b50501505..eeb9f71bd 100644 --- a/oracle/translations/oracle_es.ts +++ b/oracle/translations/oracle_es.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Set dedicado para tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Importador de Oracle diff --git a/oracle/translations/oracle_et.ts b/oracle/translations/oracle_et.ts index a68d71de5..9f2c560cd 100644 --- a/oracle/translations/oracle_et.ts +++ b/oracle/translations/oracle_et.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Nukk-komplekt mis sisaldab märgistusi @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle sissetooja diff --git a/oracle/translations/oracle_fi.ts b/oracle/translations/oracle_fi.ts index d32263f7e..c7657cf7a 100644 --- a/oracle/translations/oracle_fi.ts +++ b/oracle/translations/oracle_fi.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Tokeneja sisältävä mallisetti @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle-lataaja diff --git a/oracle/translations/oracle_fr.ts b/oracle/translations/oracle_fr.ts index d43dfa9f3..47ab5d0fa 100644 --- a/oracle/translations/oracle_fr.ts +++ b/oracle/translations/oracle_fr.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Fausse édition contenant les jetons @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Importateur Oracle diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index bea5af275..34bcb3cbc 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -277,7 +277,7 @@ e pedine che verranno usate da Cockatrice. OracleImporter - + Dummy set containing tokens Set finto contenente i token @@ -285,7 +285,7 @@ e pedine che verranno usate da Cockatrice. OracleWizard - + Oracle Importer Oracle Importer diff --git a/oracle/translations/oracle_ja.ts b/oracle/translations/oracle_ja.ts index 8d15cec34..8319f37a2 100644 --- a/oracle/translations/oracle_ja.ts +++ b/oracle/translations/oracle_ja.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens ダミーセットを含むトークン @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer - オラクル・インポーター diff --git a/oracle/translations/oracle_ko.ts b/oracle/translations/oracle_ko.ts index f69ad2fab..c5c24583f 100644 --- a/oracle/translations/oracle_ko.ts +++ b/oracle/translations/oracle_ko.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 토큰 정보가 들어있는 더미 확장판 @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer 오라클 diff --git a/oracle/translations/oracle_nb.ts b/oracle/translations/oracle_nb.ts index 191e156ef..b40868cc1 100644 --- a/oracle/translations/oracle_nb.ts +++ b/oracle/translations/oracle_nb.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dummy sett som inneholder tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle importerer diff --git a/oracle/translations/oracle_nl.ts b/oracle/translations/oracle_nl.ts index 18ca92dc2..ed9a5fae9 100644 --- a/oracle/translations/oracle_nl.ts +++ b/oracle/translations/oracle_nl.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Token voorbeeldset @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle importer diff --git a/oracle/translations/oracle_pl.ts b/oracle/translations/oracle_pl.ts index 643bb6669..d5d632f70 100644 --- a/oracle/translations/oracle_pl.ts +++ b/oracle/translations/oracle_pl.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dodatek-atrapa, zawierający tokeny. @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle – kreator importu diff --git a/oracle/translations/oracle_pt.ts b/oracle/translations/oracle_pt.ts index 309696e8c..c578a0365 100644 --- a/oracle/translations/oracle_pt.ts +++ b/oracle/translations/oracle_pt.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Set básico contendo fichas @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Importar Oracle diff --git a/oracle/translations/oracle_pt_BR.ts b/oracle/translations/oracle_pt_BR.ts index b72de5c27..96a6382ec 100644 --- a/oracle/translations/oracle_pt_BR.ts +++ b/oracle/translations/oracle_pt_BR.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Esta expansão contém fichas. @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Importador Oracle diff --git a/oracle/translations/oracle_ru.ts b/oracle/translations/oracle_ru.ts index 2029ee7e9..e2ff5b5a0 100644 --- a/oracle/translations/oracle_ru.ts +++ b/oracle/translations/oracle_ru.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Пример сета с фишками @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Импортер Oracle diff --git a/oracle/translations/oracle_sr.ts b/oracle/translations/oracle_sr.ts index de8122316..04d48688a 100644 --- a/oracle/translations/oracle_sr.ts +++ b/oracle/translations/oracle_sr.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer diff --git a/oracle/translations/oracle_tr.ts b/oracle/translations/oracle_tr.ts index 345de6f46..517f9248e 100644 --- a/oracle/translations/oracle_tr.ts +++ b/oracle/translations/oracle_tr.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer diff --git a/oracle/translations/oracle_yue.ts b/oracle/translations/oracle_yue.ts index 173ba8075..13a621b9e 100644 --- a/oracle/translations/oracle_yue.ts +++ b/oracle/translations/oracle_yue.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌組 @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle導入器 diff --git a/oracle/translations/oracle_zh-Hans.ts b/oracle/translations/oracle_zh-Hans.ts index d3927fff8..ff19ab463 100644 --- a/oracle/translations/oracle_zh-Hans.ts +++ b/oracle/translations/oracle_zh-Hans.ts @@ -276,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌组 @@ -284,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle导入器 diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index 692a0fdba..b0ee201bf 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -76,8 +76,9 @@ IslInterface::~IslInterface() QMapIterator roomUsers(room->getExternalUsers()); while (roomUsers.hasNext()) { roomUsers.next(); - if (roomUsers.value().getUserInfo()->server_id() == serverId) + if (roomUsers.value().getUserInfo()->server_id() == serverId) { emit externalRoomUserLeft(room->getId(), roomUsers.key()); + } } room->usersLock.unlock(); } @@ -87,8 +88,9 @@ IslInterface::~IslInterface() QMapIterator extUsers(server->getExternalUsers()); while (extUsers.hasNext()) { extUsers.next(); - if (extUsers.value()->getUserInfo()->server_id() == serverId) + if (extUsers.value()->getUserInfo()->server_id() == serverId) { emit externalUserLeft(extUsers.key()); + } } server->clientsLock.unlock(); } @@ -101,11 +103,12 @@ void IslInterface::initServer() QList serverList = server->getServerList(); int listIndex = -1; - for (int i = 0; i < serverList.size(); ++i) + for (int i = 0; i < serverList.size(); ++i) { if (serverList[i].address == socket->peerAddress()) { listIndex = i; break; } + } if (listIndex == -1) { logger->logMessage( QString("[ISL] address %1 unknown, terminating connection").arg(socket->peerAddress().toString())); @@ -125,9 +128,9 @@ void IslInterface::initServer() return; } - if (serverList[listIndex].cert == socket->peerCertificate()) + if (serverList[listIndex].cert == socket->peerCertificate()) { logger->logMessage(QString("[ISL] Peer authenticated as " + serverList[listIndex].hostname)); - else { + } else { logger->logMessage(QString("[ISL] Authentication failed, terminating connection")); deleteLater(); return; @@ -139,8 +142,9 @@ void IslInterface::initServer() server->clientsLock.lockForRead(); QMapIterator userIterator(server->getUsers()); - while (userIterator.hasNext()) + while (userIterator.hasNext()) { event.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(true, true)); + } server->clientsLock.unlock(); server->roomsLock.lockForRead(); @@ -217,8 +221,9 @@ void IslInterface::initClient() void IslInterface::flushOutputBuffer() { QMutexLocker locker(&outputBufferMutex); - if (outputBuffer.isEmpty()) + if (outputBuffer.isEmpty()) { return; + } server->incTxBytes(outputBuffer.size()); socket->write(outputBuffer); socket->flush(); @@ -240,11 +245,13 @@ void IslInterface::readClient() ((quint32)(unsigned char)inputBuffer[3]); inputBuffer.remove(0, 4); messageInProgress = true; - } else + } else { return; + } } - if (inputBuffer.size() < messageLength) + if (inputBuffer.size() < messageLength) { return; + } IslMessage newMessage; bool ok = newMessage.ParseFromArray(inputBuffer.data(), messageLength); diff --git a/servatrice/src/main.cpp b/servatrice/src/main.cpp index b1294a04c..9e7fe38d9 100644 --- a/servatrice/src/main.cpp +++ b/servatrice/src/main.cpp @@ -69,19 +69,22 @@ void testRNG() for (int i = 0; i <= maxMax - min; ++i) { std::cerr << (min + i); for (auto &number : numbers) { - if (i < number.size()) + if (i < number.size()) { std::cerr << "\t" << number[i]; - else + } else { std::cerr << "\t"; + } } std::cerr << std::endl; } std::cerr << std::endl << "Chi^2 ="; - for (double j : chisq) + for (double j : chisq) { std::cerr << "\t" << QString::number(j, 'f', 3).toStdString(); + } std::cerr << std::endl << "k ="; - for (int j = 0; j < chisq.size(); ++j) + for (int j = 0; j < chisq.size(); ++j) { std::cerr << "\t" << (j - min + minMax); + } std::cerr << std::endl << std::endl; } @@ -90,8 +93,9 @@ void testHash() const int n = 5000; std::cerr << "Benchmarking password hash function (n =" << n << ")..." << std::endl; QDateTime startTime = QDateTime::currentDateTime(); - for (int i = 0; i < n; ++i) + for (int i = 0; i < n; ++i) { PasswordHasher::computeHash("aaaaaa", "aaaaaaaaaaaaaaaa"); + } QDateTime endTime = QDateTime::currentDateTime(); std::cerr << startTime.secsTo(endTime) << "secs" << std::endl; } @@ -157,10 +161,11 @@ int main(int argc, char *argv[]) QMetaObject::invokeMethod(logger, "startLog", Qt::BlockingQueuedConnection, Q_ARG(QString, settingsCache->value("server/logfile", QString("server.log")).toString())); - if (logToConsole) + if (logToConsole) { qInstallMessageHandler(myMessageOutput); - else + } else { qInstallMessageHandler(myMessageOutput2); + } signalhandler = new SignalHandler(); diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 410bf4ed9..aa50e068a 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -318,8 +318,9 @@ bool Servatrice::initServer() query2->bindValue(":id_room", query->value(0).toInt()); servatriceDatabaseInterface->execSqlQuery(query2); QStringList gameTypes; - while (query2->next()) + while (query2->next()) { gameTypes.append(query2->value(0).toString()); + } addRoom(new Server_Room(query->value(0).toInt(), query->value(7).toInt(), query->value(1).toString(), query->value(2).toString(), query->value(3).toString().toLower(), query->value(4).toString().toLower(), static_cast(query->value(5).toInt()), @@ -362,21 +363,25 @@ bool Servatrice::initServer() qDebug() << "Connecting to ISL network."; qDebug() << "Loading certificate..."; QFile certFile(getISLNetworkSSLCertFile()); - if (!certFile.open(QIODevice::ReadOnly)) + if (!certFile.open(QIODevice::ReadOnly)) { throw QString("Error opening certificate file: %1").arg(getISLNetworkSSLCertFile()); + } QSslCertificate cert(&certFile); const QDateTime currentTime = QDateTime::currentDateTime(); - if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate() || cert.isBlacklisted()) + if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate() || cert.isBlacklisted()) { throw QString("Invalid certificate."); + } qDebug() << "Loading private key..."; QFile keyFile(getISLNetworkSSLKeyFile()); - if (!keyFile.open(QIODevice::ReadOnly)) + if (!keyFile.open(QIODevice::ReadOnly)) { throw QString("Error opening private key file: %1").arg(getISLNetworkSSLKeyFile()); + } QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); - if (key.isNull()) + if (key.isNull()) { throw QString("Invalid private key."); + } QMutableListIterator serverIterator(serverList); while (serverIterator.hasNext()) { @@ -401,10 +406,11 @@ bool Servatrice::initServer() qDebug() << "Starting ISL server on port" << getISLNetworkPort(); islServer = new Servatrice_IslServer(this, cert, key, this); - if (islServer->listen(QHostAddress::Any, static_cast(getISLNetworkPort()))) + if (islServer->listen(QHostAddress::Any, static_cast(getISLNetworkPort()))) { qDebug() << "ISL server listening."; - else + } else { throw QString("islServer->listen()"); + } } } catch (QString &error) { qDebug() << "ERROR --" << error; @@ -429,9 +435,9 @@ bool Servatrice::initServer() gameServer->setMaxPendingConnections(1000); QHostAddress tcpHost = getServerTCPHost(); qDebug() << "Starting server on host" << tcpHost.toString() << "port" << getServerTCPPort(); - if (gameServer->listen(tcpHost, static_cast(getServerTCPPort()))) + if (gameServer->listen(tcpHost, static_cast(getServerTCPPort()))) { qDebug() << "Server listening."; - else { + } else { qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); return false; } @@ -445,9 +451,9 @@ bool Servatrice::initServer() QHostAddress webSocketHost = getServerWebSocketHost(); qDebug() << "Starting websocket server on host" << webSocketHost.toString() << "port" << getServerWebSocketPort(); - if (websocketGameServer->listen(webSocketHost, static_cast(getServerWebSocketPort()))) + if (websocketGameServer->listen(webSocketHost, static_cast(getServerWebSocketPort()))) { qDebug() << "Websocket server listening."; - else { + } else { qDebug() << "websocketGameServer->listen(): Error:" << websocketGameServer->errorString(); return false; } @@ -455,11 +461,12 @@ bool Servatrice::initServer() if (getIdleClientTimeout() > 0) { qDebug() << "Idle client timeout value:" << getIdleClientTimeout(); - if (getIdleClientTimeout() < 300) + if (getIdleClientTimeout() < 300) { qDebug() << "WARNING: It is not recommended to set the IdleClientTimeout value very low. Doing so will " "cause clients to very quickly be disconnected. Many players when connected may be searching " "for card details outside the client in the middle of matches or possibly drafting outside the " "client and short time out values will remove these players."; + } } setRequiredFeatures(getRequiredFeatures()); @@ -511,9 +518,11 @@ int Servatrice::getUsersWithAddress(const QHostAddress &address) const { int result = 0; QReadLocker locker(&clientsLock); - for (auto client : clients) - if (static_cast(client)->getPeerAddress() == address) + for (auto client : clients) { + if (static_cast(client)->getPeerAddress() == address) { ++result; + } + } return result; } @@ -522,21 +531,24 @@ QList Servatrice::getUsersWithAddressAsList(con { QList result; QReadLocker locker(&clientsLock); - for (auto client : clients) - if (static_cast(client)->getPeerAddress() == address) + for (auto client : clients) { + if (static_cast(client)->getPeerAddress() == address) { result.append(static_cast(client)); + } + } return result; } void Servatrice::updateLoginMessage() { - if (!servatriceDatabaseInterface->checkSql()) + if (!servatriceDatabaseInterface->checkSql()) { return; + } QSqlQuery *query = servatriceDatabaseInterface->prepareQuery( "select message from {prefix}_servermessages where id_server = :id_server order by timest desc limit 1"); query->bindValue(":id_server", serverId); - if (servatriceDatabaseInterface->execSqlQuery(query)) + if (servatriceDatabaseInterface->execSqlQuery(query)) { if (query->next()) { const QString newLoginMessage = query->value(0).toString(); @@ -548,10 +560,12 @@ void Servatrice::updateLoginMessage() event.set_message(newLoginMessage.toStdString()); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); QMapIterator usersIterator(users); - while (usersIterator.hasNext()) + while (usersIterator.hasNext()) { usersIterator.next().value()->sendProtocolItem(*se); + } delete se; } + } } void Servatrice::setRequiredFeatures(const QString &featureList) @@ -560,18 +574,20 @@ void Servatrice::setRequiredFeatures(const QString &featureList) serverRequiredFeatureList.clear(); features.initalizeFeatureList(serverRequiredFeatureList); QStringList listReqFeatures = featureList.split(",", Qt::SkipEmptyParts); - if (!listReqFeatures.isEmpty()) + if (!listReqFeatures.isEmpty()) { for (const QString &reqFeature : listReqFeatures) { features.enableRequiredFeature(serverRequiredFeatureList, reqFeature); } + } qDebug() << "Set required client features to:" << serverRequiredFeatureList; } void Servatrice::statusUpdate() { - if (!servatriceDatabaseInterface->checkSql()) + if (!servatriceDatabaseInterface->checkSql()) { return; + } const int uc = getUsersCount(); // for correct mutex locking order @@ -611,8 +627,9 @@ void Servatrice::statusUpdate() auto servDbSelQuery = servatriceDatabaseInterface->prepareQuery("select a.name, b.email, b.token from " "{prefix}_activation_emails a left join " "{prefix}_users b on a.name = b.name"); - if (!servatriceDatabaseInterface->execSqlQuery(servDbSelQuery)) + if (!servatriceDatabaseInterface->execSqlQuery(servDbSelQuery)) { return; + } auto *queryDelete = servatriceDatabaseInterface->prepareQuery("delete from {prefix}_activation_emails where name = :name"); @@ -633,8 +650,9 @@ void Servatrice::statusUpdate() auto *forgotPwQuery = servatriceDatabaseInterface->prepareQuery( "select a.name, b.email, b.token from {prefix}_forgot_password a left join {prefix}_users b on a.name " "= b.name where a.emailed = 0"); - if (!servatriceDatabaseInterface->execSqlQuery(forgotPwQuery)) + if (!servatriceDatabaseInterface->execSqlQuery(forgotPwQuery)) { return; + } QSqlQuery *queryDelete = servatriceDatabaseInterface->prepareQuery( "update {prefix}_forgot_password set emailed = 1 where name = :name"); @@ -686,8 +704,9 @@ void Servatrice::shutdownTimeout() { // Show every time counter cut in half & every minute for last 5 minutes if (shutdownMinutes <= 5 || shutdownMinutes == nextShutdownMessageMinutes) { - if (shutdownMinutes == nextShutdownMessageMinutes) + if (shutdownMinutes == nextShutdownMessageMinutes) { nextShutdownMessageMinutes = shutdownMinutes / 2; + } SessionEvent *se; if (shutdownMinutes) { @@ -702,8 +721,9 @@ void Servatrice::shutdownTimeout() } clientsLock.lockForRead(); - for (auto &client : clients) + for (auto &client : clients) { client->sendProtocolItem(*se); + } clientsLock.unlock(); delete se; @@ -759,12 +779,14 @@ void Servatrice::doSendIslMessage(const IslMessage &msg, int _serverId) if (_serverId == -1) { QMapIterator islIterator(islInterfaces); - while (islIterator.hasNext()) + while (islIterator.hasNext()) { islIterator.next().value()->transmitMessage(msg); + } } else { IslInterface *interface = islInterfaces.value(_serverId); - if (interface) + if (interface) { interface->transmitMessage(msg); + } } } @@ -968,10 +990,11 @@ bool Servatrice::permitCreateGameAsJudge() const QHostAddress Servatrice::getServerTCPHost() const { QString host = settingsCache->value("server/host", "any").toString(); - if (host == "any") + if (host == "any") { return QHostAddress::Any; - else + } else { return QHostAddress(host); + } } int Servatrice::getServerTCPPort() const @@ -987,10 +1010,11 @@ int Servatrice::getNumberOfWebSocketPools() const QHostAddress Servatrice::getServerWebSocketHost() const { QString host = settingsCache->value("server/websocket_host", "any").toString(); - if (host == "any") + if (host == "any") { return QHostAddress::Any; - else + } else { return QHostAddress(host); + } } int Servatrice::getServerWebSocketPort() const diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index bce3542e8..73643825e 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -55,8 +55,9 @@ bool Servatrice_DatabaseInterface::initDatabase(const QString &type, bool Servatrice_DatabaseInterface::openDatabase() { - if (sqlDatabase.isOpen()) + if (sqlDatabase.isOpen()) { sqlDatabase.close(); + } const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qCDebug(DatabaseInterfaceLog).noquote() << poolStr << "Opening database..."; @@ -139,8 +140,9 @@ QSqlQuery *Servatrice_DatabaseInterface::prepareQuery(const QString &queryText) bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query) { - if (query->exec()) + if (query->exec()) { return true; + } const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qCCritical(DatabaseInterfaceLog) << poolStr << "Error executing query:" << query->lastError().text(); sqlDatabase.close(); @@ -151,8 +153,9 @@ bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query) bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString &error) { int minNameLength = settingsCache->value("users/minnamelength", 6).toInt(); - if (minNameLength < 1) + if (minNameLength < 1) { minNameLength = 1; + } int maxNameLength = settingsCache->value("users/maxnamelength", 12).toInt(); bool allowLowercase = settingsCache->value("users/allowlowercase", true).toBool(); bool allowUppercase = settingsCache->value("users/allowuppercase", true).toBool(); @@ -184,29 +187,36 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString .arg(disallowedWordsStr) .arg(disallowedRegExpStr); - if (user.length() < minNameLength || user.length() > maxNameLength) + if (user.length() < minNameLength || user.length() > maxNameLength) { return false; + } - if (!allowPunctuationPrefix && allowedPunctuation.contains(user.at(0))) + if (!allowPunctuationPrefix && allowedPunctuation.contains(user.at(0))) { return false; + } for (const QString &word : disallowedWords) { - if (user.contains(word, Qt::CaseInsensitive)) + if (user.contains(word, Qt::CaseInsensitive)) { return false; + } } for (const QRegularExpression ®Exp : settingsCache->disallowedRegExp) { - if (regExp.match(user).hasMatch()) + if (regExp.match(user).hasMatch()) { return false; + } } QString regEx("\\A["); - if (allowLowercase) + if (allowLowercase) { regEx.append("a-z"); - if (allowUppercase) + } + if (allowUppercase) { regEx.append("A-Z"); - if (allowNumerics) + } + if (allowNumerics) { regEx.append("0-9"); + } regEx.append(QRegularExpression::escape(allowedPunctuation)); regEx.append("]+\\z"); @@ -222,8 +232,9 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &country, bool active) { - if (!checkSql()) + if (!checkSql()) { return false; + } QString passwordSha512; if (passwordNeedsHash) { @@ -259,8 +270,9 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const QString &token) { - if (!checkSql()) + if (!checkSql()) { return false; + } QSqlQuery *activateQuery = prepareQuery("select name from {prefix}_users where active=0 and name=:username and token=:token"); @@ -306,20 +318,24 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot return UnknownUser; case Servatrice::AuthenticationPassword: { QString configPassword = settingsCache->value("authentication/password").toString(); - if (configPassword == password) + if (configPassword == password) { return PasswordRight; + } return NotLoggedIn; } case Servatrice::AuthenticationSql: { - if (!checkSql()) + if (!checkSql()) { return UnknownUser; + } - if (!usernameIsValid(user, reasonStr)) + if (!usernameIsValid(user, reasonStr)) { return UsernameInvalid; + } - if (checkUserIsBanned(handler->getAddress(), user, clientId, reasonStr, banSecondsLeft)) + if (checkUserIsBanned(handler->getAddress(), user, clientId, reasonStr, banSecondsLeft)) { return UserIsBanned; + } QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); @@ -364,8 +380,9 @@ bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining) { - if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { return false; + } if (!checkSql()) { qCWarning(DatabaseInterfaceLog) << "Failed to check if user is banned. Database invalid."; @@ -381,8 +398,9 @@ bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining) { - if (clientId.isEmpty()) + if (clientId.isEmpty()) { return false; + } QSqlQuery *idBanQuery = prepareQuery("select" @@ -487,8 +505,9 @@ bool Servatrice_DatabaseInterface::activeUserExists(const QString &user) QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name and active = 1"); query->bindValue(":name", user); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } return query->next(); } return false; @@ -501,8 +520,9 @@ bool Servatrice_DatabaseInterface::userExists(const QString &user) QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name"); query->bindValue(":name", user); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } return query->next(); } return false; @@ -535,10 +555,12 @@ int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { QSqlQuery *query = prepareQuery("select id from {prefix}_users where name = :name and active = 1"); query->bindValue(":name", name); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return -1; - if (!query->next()) + } + if (!query->next()) { return -1; + } return query->value(0).toInt(); } return -1; @@ -546,11 +568,13 @@ int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const QString &who) { - if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) { return false; + } - if (!checkSql()) + if (!checkSql()) { return false; + } int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); @@ -559,18 +583,21 @@ bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const prepareQuery("select 1 from {prefix}_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); query->bindValue(":id_user1", id1); query->bindValue(":id_user2", id2); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } return query->next(); } bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, const QString &who) { - if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) { return false; + } - if (!checkSql()) + if (!checkSql()) { return false; + } int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); @@ -579,8 +606,9 @@ bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, cons prepareQuery("select 1 from {prefix}_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); query->bindValue(":id_user1", id1); query->bindValue(":id_user2", id2); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } return query->next(); } @@ -588,29 +616,34 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer { ServerInfo_User result; - if (withId) + if (withId) { result.set_id(query->value(0).toInt()); + } result.set_name(query->value(1).toString().toStdString()); const int is_admin = query->value(2).toInt(); int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; - if (is_admin & 1) + if (is_admin & 1) { userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; - else if (is_admin & 2) + } else if (is_admin & 2) { userLevel |= ServerInfo_User::IsModerator; + } - if (is_admin & 4) + if (is_admin & 4) { userLevel |= ServerInfo_User::IsJudge; + } result.set_user_level(userLevel); const QString country = query->value(3).toString(); - if (!country.isEmpty()) + if (!country.isEmpty()) { result.set_country(country.toStdString()); + } const QString privlevel = query->value(4).toString(); - if (!privlevel.isEmpty()) + if (!privlevel.isEmpty()) { result.set_privlevel(privlevel.toStdString()); + } const auto &pawn_left_override = query->value(5).toString(); const auto &pawn_right_override = query->value(6).toString(); @@ -623,12 +656,14 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer if (complete) { const QString realName = query->value(7).toString(); - if (!realName.isEmpty()) + if (!realName.isEmpty()) { result.set_real_name(realName.toStdString()); + } const QByteArray avatarBmp = query->value(8).toByteArray(); - if (avatarBmp.size()) + if (avatarBmp.size()) { result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); + } const QDateTime regDate = query->value(9).toDateTime(); if (!regDate.toString(Qt::ISODate).isEmpty()) { @@ -638,12 +673,14 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer } const QString email = query->value(10).toString(); - if (!email.isEmpty()) + if (!email.isEmpty()) { result.set_email(email.toStdString()); + } const QString clientid = query->value(11).toString(); - if (!clientid.isEmpty()) + if (!clientid.isEmpty()) { result.set_clientid(clientid.toStdString()); + } } return result; } @@ -655,23 +692,27 @@ ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, b result.set_user_level(ServerInfo_User::IsUser); if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { - if (!checkSql()) + if (!checkSql()) { return result; + } QSqlQuery *query = prepareQuery("select id, name, admin, country, privlevel, leftPawnColorOverride, " "rightPawnColorOverride, realname, avatar_bmp, registrationDate, " "email, clientid from {prefix}_users where " "name = :name and active = 1"); query->bindValue(":name", name); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return result; + } - if (query->next()) + if (query->next()) { return evalUserQueryResult(query, true, withId); - else + } else { return result; - } else + } + } else { return result; + } } void Servatrice_DatabaseInterface::clearSessionTables() @@ -715,11 +756,13 @@ qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &clientId, const QString &connectionType) { - if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) { return -1; + } - if (!checkSql()) + if (!checkSql()) { return -1; + } QSqlQuery *query = prepareQuery("insert into {prefix}_sessions (user_name, id_server, ip_address, start_time, " "clientid, connection_type) values(:user_name, :id_server, :ip_address, NOW(), " @@ -729,18 +772,21 @@ qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, query->bindValue(":ip_address", address); query->bindValue(":client_id", clientId); query->bindValue(":connection_type", connectionType); - if (execSqlQuery(query)) + if (execSqlQuery(query)) { return query->lastInsertId().toInt(); + } return -1; } void Servatrice_DatabaseInterface::endSession(qint64 sessionId) { - if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) { return; + } - if (!checkSql()) + if (!checkSql()) { return; + } auto *query = prepareQuery("update {prefix}_sessions set end_time=NOW() where id = :id_session"); query->bindValue(":id_session", sessionId); @@ -759,8 +805,9 @@ QMap Servatrice_DatabaseInterface::getBuddyList(const "left join {prefix}_buddylist b on a.id = b.id_user2 left join {prefix}_users " "c on b.id_user1 = c.id where c.name = :name"); query->bindValue(":name", name); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return result; + } while (query->next()) { const ServerInfo_User &temp = evalUserQueryResult(query, false); @@ -782,8 +829,9 @@ QMap Servatrice_DatabaseInterface::getIgnoreList(const "left join {prefix}_ignorelist b on a.id = b.id_user2 left join {prefix}_users " "c on b.id_user1 = c.id where c.name = :name"); query->bindValue(":name", name); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return result; + } while (query->next()) { ServerInfo_User temp = evalUserQueryResult(query, false); @@ -795,11 +843,13 @@ QMap Servatrice_DatabaseInterface::getIgnoreList(const int Servatrice_DatabaseInterface::getNextGameId() { - if (!sqlDatabase.isValid()) + if (!sqlDatabase.isValid()) { return server->getNextLocalGameId(); + } - if (!checkSql()) + if (!checkSql()) { return -1; + } QSqlQuery *query = prepareQuery("insert into {prefix}_games (time_started) values (now())"); @@ -812,8 +862,9 @@ int Servatrice_DatabaseInterface::getNextGameId() int Servatrice_DatabaseInterface::getNextReplayId() { - if (!checkSql()) + if (!checkSql()) { return -1; + } QSqlQuery *query = prepareQuery("insert into {prefix}_replays (id_game) values (NULL)"); @@ -831,11 +882,13 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, const QSet &allSpectatorsEver, const QList &replayList) { - if (!checkSql()) + if (!checkSql()) { return; + } - if (!settingsCache->value("game/store_replays", 1).toBool()) + if (!settingsCache->value("game/store_replays", 1).toBool()) { return; + } QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; QSetIterator playerIterator(allPlayersEver); @@ -848,8 +901,9 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, QSetIterator allUsersIterator(allUsersInGame); while (allUsersIterator.hasNext()) { int id = getUserIdInDB(allUsersIterator.next()); - if (id == -1) + if (id == -1) { continue; + } gameIds2.append(gameInfo.game_id()); userIds.append(id); replayNames.append(QString::fromStdString(gameInfo.description())); @@ -888,8 +942,9 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, query->bindValue(":password", gameInfo.with_password() ? 1 : 0); query->bindValue(":game_types", roomGameTypes.isEmpty() ? QString("") : roomGameTypes.join(", ")); query->bindValue(":player_count", gameInfo.max_players()); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return; + } } { QSqlQuery *query = @@ -926,8 +981,9 @@ DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, int user query->bindValue(":id", deckId); query->bindValue(":id_user", userId); execSqlQuery(query); - if (!query->next()) + if (!query->next()) { throw Response::RespNameNotFound; + } DeckList *deck = new DeckList; deck->loadFromString_Native(query->value(0).toString()); @@ -946,23 +1002,27 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, QString targetTypeString; switch (targetType) { case MessageTargetRoom: - if (!settingsCache->value("logging/log_user_msg_room", 0).toBool()) + if (!settingsCache->value("logging/log_user_msg_room", 0).toBool()) { return; + } targetTypeString = "room"; break; case MessageTargetGame: - if (!settingsCache->value("logging/log_user_msg_game", 0).toBool()) + if (!settingsCache->value("logging/log_user_msg_game", 0).toBool()) { return; + } targetTypeString = "game"; break; case MessageTargetChat: - if (!settingsCache->value("logging/log_user_msg_chat", 0).toBool()) + if (!settingsCache->value("logging/log_user_msg_chat", 0).toBool()) { return; + } targetTypeString = "chat"; break; case MessageTargetIslRoom: - if (!settingsCache->value("logging/log_user_msg_isl", 0).toBool()) + if (!settingsCache->value("logging/log_user_msg_isl", 0).toBool()) { return; + } targetTypeString = "room"; break; default: @@ -995,8 +1055,9 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, "passwordLastChangedDate = NOW() where name = :name"); passwordQuery->bindValue(":password", passwordSha512); passwordQuery->bindValue(":name", user); - if (execSqlQuery(passwordQuery)) + if (execSqlQuery(passwordQuery)) { return true; + } return false; } @@ -1007,15 +1068,18 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, const QString &newPassword, bool newPasswordNeedsHash) { - if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { return false; + } - if (!checkSql()) + if (!checkSql()) { return false; + } QString error; - if (!usernameIsValid(user, error)) + if (!usernameIsValid(user, error)) { return false; + } QSqlQuery *passwordQuery = prepareQuery("select password_sha512 from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); @@ -1025,8 +1089,9 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, return false; } - if (!passwordQuery->next()) + if (!passwordQuery->next()) { return false; + } const QString correctPasswordSha512 = passwordQuery->value(0).toString(); QString oldPasswordSha512 = oldPassword; @@ -1034,8 +1099,9 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, QString salt = correctPasswordSha512.left(16); oldPasswordSha512 = PasswordHasher::computeHash(oldPassword, salt); } - if (correctPasswordSha512 != oldPasswordSha512) + if (correctPasswordSha512 != oldPasswordSha512) { return false; + } return changeUserPassword(user, newPassword, newPasswordNeedsHash); } @@ -1044,23 +1110,28 @@ int Servatrice_DatabaseInterface::getActiveUserCount(QString connectionType) { int userCount = 0; - if (!checkSql()) + if (!checkSql()) { return userCount; + } QString text = "select count(*) from {prefix}_sessions where id_server = :serverid AND end_time is NULL"; - if (!connectionType.isEmpty()) + if (!connectionType.isEmpty()) { text += " AND connection_type = :connection_type"; + } QSqlQuery *query = prepareQuery(text); query->bindValue(":serverid", server->getServerID()); - if (!connectionType.isEmpty()) + if (!connectionType.isEmpty()) { query->bindValue(":connection_type", connectionType); + } - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return userCount; + } - if (query->next()) + if (query->next()) { userCount = query->value(0).toInt(); + } return userCount; } @@ -1068,8 +1139,9 @@ int Servatrice_DatabaseInterface::getActiveUserCount(QString connectionType) void Servatrice_DatabaseInterface::updateUsersClientID(const QString &userName, const QString &userClientID) { - if (!checkSql()) + if (!checkSql()) { return; + } QSqlQuery *query = prepareQuery("update {prefix}_users set clientid = :clientid where name = :username"); query->bindValue(":clientid", userClientID); @@ -1080,8 +1152,9 @@ void Servatrice_DatabaseInterface::updateUsersClientID(const QString &userName, void Servatrice_DatabaseInterface::updateUsersLastLoginData(const QString &userName, const QString &clientVersion) { - if (!checkSql()) + if (!checkSql()) { return; + } int usersID = 0; @@ -1100,8 +1173,9 @@ void Servatrice_DatabaseInterface::updateUsersLastLoginData(const QString &userN int userCount = 0; query = prepareQuery("select count(id) from {prefix}_user_analytics where id = :user_id"); query->bindValue(":user_id", usersID); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return; + } if (query->next()) { userCount = query->value(0).toInt(); @@ -1128,8 +1202,9 @@ QList Servatrice_DatabaseInterface::getUserBanHistory(const QStr QList results; ServerInfo_Ban banDetails; - if (!checkSql()) + if (!checkSql()) { return results; + } QSqlQuery *query = prepareQuery("SELECT A.id_admin, A.time_from, A.minutes, A.reason, A.visible_reason, B.name AS name_admin FROM " @@ -1159,8 +1234,9 @@ bool Servatrice_DatabaseInterface::addWarning(const QString userName, const QString warningReason, const QString clientID) { - if (!checkSql()) + if (!checkSql()) { return false; + } int userID = getUserIdInDB(userName); QSqlQuery *query = @@ -1184,8 +1260,9 @@ QList Servatrice_DatabaseInterface::getUserWarnHistory(const QList results; ServerInfo_Warning warnDetails; - if (!checkSql()) + if (!checkSql()) { return results; + } int userID = getUserIdInDB(userName); QSqlQuery *query = @@ -1223,8 +1300,9 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory QList results; ServerInfo_ChatMessage chatMessage; - if (!checkSql()) + if (!checkSql()) { return results; + } if (user.isEmpty() && ipaddress.isEmpty() && gameid.isEmpty() && gamename.isEmpty()) { // To ensure quick results and minimal lag, require an indexed field @@ -1233,48 +1311,58 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory // BUILD QUERY STRING BASED ON PASSED IN VALUES QString queryString = "SELECT * FROM {prefix}_log WHERE `sender_ip` IS NOT NULL"; - if (!user.isEmpty()) + if (!user.isEmpty()) { queryString.append(" AND (`sender_name` = :user_name OR `target_name` = :user_name)"); + } - if (!ipaddress.isEmpty()) + if (!ipaddress.isEmpty()) { queryString.append(" AND `sender_ip` = :ip_to_find"); + } - if (!gameid.isEmpty()) + if (!gameid.isEmpty()) { queryString.append(" AND (`target_id` = :game_id AND `target_type` = 'game')"); + } - if (!gamename.isEmpty()) + if (!gamename.isEmpty()) { queryString.append(" AND (`target_name` = :game_name AND `target_type` = 'game')"); + } - if (!message.isEmpty()) + if (!message.isEmpty()) { queryString.append(" AND `log_message` LIKE :log_message"); + } if (chat || game || room) { queryString.append(" AND ("); - if (chat) + if (chat) { queryString.append("`target_type` = 'chat'"); + } if (game) { - if (chat) + if (chat) { queryString.append(" OR `target_type` = 'game'"); - else + } else { queryString.append("`target_type` = 'game'"); + } } if (room) { - if (game || chat) + if (game || chat) { queryString.append(" OR `target_type` = 'room'"); - else + } else { queryString.append("`target_type` = 'room'"); + } } queryString.append(")"); } - if (range) + if (range) { queryString.append(" AND log_time >= DATE_SUB(now(), INTERVAL :range_time HOUR)"); + } - if (maxresults) + if (maxresults) { queryString.append(" LIMIT :limit_size"); + } QSqlQuery *query = prepareQuery(queryString); if (!user.isEmpty()) { @@ -1321,8 +1409,9 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory int Servatrice_DatabaseInterface::checkNumberOfUserAccounts(const QString &email) { - if (!checkSql()) + if (!checkSql()) { return 0; + } QSqlQuery *query = prepareQuery("SELECT count(email) FROM {prefix}_users WHERE email = :user_email"); query->bindValue(":user_email", email); @@ -1333,75 +1422,88 @@ int Servatrice_DatabaseInterface::checkNumberOfUserAccounts(const QString &email return 0; } - if (query->next()) + if (query->next()) { return query->value(0).toInt(); + } return 0; } bool Servatrice_DatabaseInterface::addForgotPassword(const QString &user) { - if (!checkSql()) + if (!checkSql()) { return false; + } - if (!updateUserToken(PasswordHasher::generateActivationToken(), user)) + if (!updateUserToken(PasswordHasher::generateActivationToken(), user)) { return false; + } QSqlQuery *query = prepareQuery("insert into {prefix}_forgot_password (name,requestDate) values (:username,NOW())"); query->bindValue(":username", user); - if (execSqlQuery(query)) + if (execSqlQuery(query)) { return true; + } return false; } bool Servatrice_DatabaseInterface::removeForgotPassword(const QString &user) { - if (!checkSql()) + if (!checkSql()) { return false; + } QSqlQuery *query = prepareQuery("delete from {prefix}_forgot_password where name = :username"); query->bindValue(":username", user); - if (execSqlQuery(query)) + if (execSqlQuery(query)) { return true; + } return false; } bool Servatrice_DatabaseInterface::doesForgotPasswordExist(const QString &user) { - if (!checkSql()) + if (!checkSql()) { return false; + } QSqlQuery *query = prepareQuery("select count(name) from {prefix}_forgot_password where name = :user_name AND " "requestDate > (now() - interval :minutes minute)"); query->bindValue(":user_name", user); query->bindValue(":minutes", QString::number(server->getForgotPasswordTokenLife())); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } - if (query->next()) - if (query->value("count(name)").toInt() > 0) + if (query->next()) { + if (query->value("count(name)").toInt() > 0) { return true; + } + } return false; } bool Servatrice_DatabaseInterface::updateUserToken(const QString &token, const QString &user) { - if (!checkSql()) + if (!checkSql()) { return false; + } - if (token.isEmpty() || user.isEmpty()) + if (token.isEmpty() || user.isEmpty()) { return false; + } QSqlQuery *query = prepareQuery("update {prefix}_users set token = :token where name = :user_name"); query->bindValue(":user_name", user); query->bindValue(":token", token); - if (execSqlQuery(query)) + if (execSqlQuery(query)) { return true; + } return false; } @@ -1411,22 +1513,27 @@ bool Servatrice_DatabaseInterface::validateTableColumnStringData(const QString & const QString &_user, const QString &_datatocheck) { - if (!checkSql()) + if (!checkSql()) { return false; + } - if (table.isEmpty() || column.isEmpty() || _user.isEmpty() || _datatocheck.isEmpty()) + if (table.isEmpty() || column.isEmpty() || _user.isEmpty() || _datatocheck.isEmpty()) { return false; + } QString formatedQuery = QString("select %1 from %2 where name = :user_name").arg(column).arg(table); QSqlQuery *query = prepareQuery(formatedQuery); query->bindValue(":user_name", _user); - if (!execSqlQuery(query)) + if (!execSqlQuery(query)) { return false; + } - if (query->next()) - if (query->value(column).toString().toLower() == _datatocheck.toLower()) + if (query->next()) { + if (query->value(column).toString().toLower() == _datatocheck.toLower()) { return true; + } + } return false; } @@ -1438,14 +1545,17 @@ void Servatrice_DatabaseInterface::addAuditRecord(const QString &user, const QString &details, const bool &results = false) { - if (!checkSql()) + if (!checkSql()) { return; + } - if (!server->getEnableAudit()) + if (!server->getEnableAudit()) { return; + } - if (user.isEmpty() || ipaddress.isEmpty() || clientid.isEmpty() || action.isEmpty()) + if (user.isEmpty() || ipaddress.isEmpty() || clientid.isEmpty() || action.isEmpty()) { return; + } QSqlQuery *query = prepareQuery("insert into {prefix}_audit " "(id_server,name,ip_address,clientid,incidentDate,action,results,details) values " diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index de0befacb..620780052 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -39,20 +39,23 @@ void ServerLogger::startLog(const QString &logFileName) logFile = 0; return; } - } else + } else { logFile = 0; + } connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection); } void ServerLogger::logMessage(const QString &message, void *caller) { - if (!logFile) + if (!logFile) { return; + } QString callerString; - if (caller) + if (caller) { callerString = QString::number((qulonglong)caller, 16) + " "; + } // filter out all log entries based on values in configuration file bool shouldWeWriteLog = settingsCache->value("server/writelog", 1).toBool(); @@ -60,8 +63,9 @@ void ServerLogger::logMessage(const QString &message, void *caller) QStringList listlogFilters = logFilters.split(",", Qt::SkipEmptyParts); bool shouldWeSkipLine = false; - if (!shouldWeWriteLog) + if (!shouldWeWriteLog) { return; + } if (!logFilters.trimmed().isEmpty()) { shouldWeSkipLine = true; @@ -73,8 +77,9 @@ void ServerLogger::logMessage(const QString &message, void *caller) } } - if (shouldWeSkipLine) + if (shouldWeSkipLine) { return; + } bufferMutex.lock(); buffer.append(QDateTime::currentDateTime().toString() + " " + callerString + message); @@ -84,8 +89,9 @@ void ServerLogger::logMessage(const QString &message, void *caller) void ServerLogger::flushBuffer() { - if (flushRunning) + if (flushRunning) { return; + } flushRunning = true; QTextStream stream(logFile); @@ -103,15 +109,17 @@ void ServerLogger::flushBuffer() stream << message << "\n"; stream.flush(); - if (logToConsole) + if (logToConsole) { std::cout << message.toStdString() << std::endl; + } } } void ServerLogger::rotateLogs() { - if (!logFile) + if (!logFile) { return; + } flushBuffer(); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 41e61ddec..bc90a3ef1 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -117,8 +117,9 @@ bool AbstractServerSocketInterface::initSession() // allow unlimited number of connections from the trusted sources QString trustedSources = settingsCache->value("security/trusted_sources", "127.0.0.1,::1").toString(); - if (trustedSources.contains(getAddress(), Qt::CaseInsensitive)) + if (trustedSources.contains(getAddress(), Qt::CaseInsensitive)) { return true; + } int maxUsers = servatrice->getMaxUsersPerAddress(); if ((maxUsers > 0) && (servatrice->getUsersWithAddress(getPeerAddress()) > maxUsers)) { @@ -270,35 +271,44 @@ AbstractServerSocketInterface::processExtendedAdminCommand(int cmdType, const Ad Response::ResponseCode AbstractServerSocketInterface::cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } QString list = nameFromStdString(cmd.list()); QString user = nameFromStdString(cmd.user_name()); - if ((list != "buddy") && (list != "ignore")) + if ((list != "buddy") && (list != "ignore")) { return Response::RespContextError; + } - if (list == "buddy") - if (databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + if (list == "buddy") { + if (databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) { return Response::RespContextError; - if (list == "ignore") - if (databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + } + } + if (list == "ignore") { + if (databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) { return Response::RespContextError; + } + } int id1 = userInfo->id(); int id2 = sqlInterface->getUserIdInDB(user); - if (id2 < 0) + if (id2 < 0) { return Response::RespNameNotFound; - if (id1 == id2) + } + if (id1 == id2) { return Response::RespContextError; + } QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_" + list + "list (id_user1, id_user2) values(:id1, :id2)"); query->bindValue(":id1", id1); query->bindValue(":id2", id2); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } Event_AddToList event; event.set_list_name(cmd.list()); @@ -311,33 +321,41 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAddToList(const Command Response::ResponseCode AbstractServerSocketInterface::cmdRemoveFromList(const Command_RemoveFromList &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } QString list = nameFromStdString(cmd.list()); QString user = nameFromStdString(cmd.user_name()); - if ((list != "buddy") && (list != "ignore")) + if ((list != "buddy") && (list != "ignore")) { return Response::RespContextError; + } - if (list == "buddy") - if (!databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + if (list == "buddy") { + if (!databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) { return Response::RespContextError; - if (list == "ignore") - if (!databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + } + } + if (list == "ignore") { + if (!databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) { return Response::RespContextError; + } + } int id1 = userInfo->id(); int id2 = sqlInterface->getUserIdInDB(user); - if (id2 < 0) + if (id2 < 0) { return Response::RespNameNotFound; + } QSqlQuery *query = sqlInterface->prepareQuery("delete from {prefix}_" + list + "list where id_user1 = :id1 and id_user2 = :id2"); query->bindValue(":id1", id1); query->bindValue(":id2", id2); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } Event_RemoveFromList event; event.set_list_name(cmd.list()); @@ -349,25 +367,30 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveFromList(const Co int AbstractServerSocketInterface::getDeckPathId(int basePathId, QStringList path) { - if (path.isEmpty()) + if (path.isEmpty()) { return 0; - if (path[0].isEmpty()) + } + if (path[0].isEmpty()) { return 0; + } QSqlQuery *query = sqlInterface->prepareQuery("select id from {prefix}_decklist_folders where id_parent = " ":id_parent and name = :name and id_user = :id_user"); query->bindValue(":id_parent", basePathId); query->bindValue(":name", path.takeFirst()); query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return -1; - if (!query->next()) + } + if (!query->next()) { return -1; + } int id = query->value(0).toInt(); - if (path.isEmpty()) + if (path.isEmpty()) { return id; - else + } else { return getDeckPathId(id, path); + } } int AbstractServerSocketInterface::getDeckPathId(const QString &path) @@ -381,28 +404,32 @@ bool AbstractServerSocketInterface::deckListHelper(int folderId, ServerInfo_Deck "select id, name from {prefix}_decklist_folders where id_parent = :id_parent and id_user = :id_user"); query->bindValue(":id_parent", folderId); query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return false; + } QMap results; - while (query->next()) + while (query->next()) { results[query->value(0).toInt()] = query->value(1).toString(); + } for (int key : results.keys()) { ServerInfo_DeckStorage_TreeItem *newItem = folder->add_items(); newItem->set_id(key); newItem->set_name(results.value(key).toStdString()); - if (!deckListHelper(newItem->id(), newItem->mutable_folder())) + if (!deckListHelper(newItem->id(), newItem->mutable_folder())) { return false; + } } query = sqlInterface->prepareQuery("select id, name, upload_time from {prefix}_decklist_files where id_folder = " ":id_folder and id_user = :id_user"); query->bindValue(":id_folder", folderId); query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return false; + } while (query->next()) { ServerInfo_DeckStorage_TreeItem *newItem = folder->add_items(); @@ -422,16 +449,18 @@ bool AbstractServerSocketInterface::deckListHelper(int folderId, ServerInfo_Deck Response::ResponseCode AbstractServerSocketInterface::cmdDeckList(const Command_DeckList & /*cmd*/, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } sqlInterface->checkSql(); Response_DeckList *re = new Response_DeckList; ServerInfo_DeckStorage_Folder *root = re->mutable_root(); - if (!deckListHelper(0, root)) + if (!deckListHelper(0, root)) { return Response::RespContextError; + } rc.setResponseExtension(re); return Response::RespOk; @@ -440,27 +469,31 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckList(const Command_ Response::ResponseCode AbstractServerSocketInterface::cmdDeckNewDir(const Command_DeckNewDir &cmd, ResponseContainer & /*rc*/) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } sqlInterface->checkSql(); QString path = nameFromStdString(cmd.path()); int folderId = getDeckPathId(path); - if (folderId == -1) + if (folderId == -1) { return Response::RespNameNotFound; + } QString name = nameFromStdString(cmd.dir_name()); - if (path.length() + name.length() + 1 > MAX_NAME_LENGTH) + if (path.length() + name.length() + 1 > MAX_NAME_LENGTH) { return Response::RespContextError; // do not allow creation of paths that would be too long to delete + } QSqlQuery *query = sqlInterface->prepareQuery( "insert into {prefix}_decklist_folders (id_parent, id_user, name) values(:id_parent, :id_user, :name)"); query->bindValue(":id_parent", folderId); query->bindValue(":id_user", userInfo->id()); query->bindValue(":name", name); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespContextError; + } return Response::RespOk; } @@ -471,8 +504,9 @@ void AbstractServerSocketInterface::deckDelDirHelper(int basePathId) sqlInterface->prepareQuery("select id from {prefix}_decklist_folders where id_parent = :id_parent"); query->bindValue(":id_parent", basePathId); sqlInterface->execSqlQuery(query); - while (query->next()) + while (query->next()) { deckDelDirHelper(query->value(0).toInt()); + } query = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id_folder = :id_folder"); query->bindValue(":id_folder", basePathId); @@ -487,8 +521,9 @@ void AbstractServerSocketInterface::sendServerMessage(const QString userName, co { AbstractServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (!user) + if (!user) { return; + } Event_UserMessage event; event.set_sender_name("Servatrice"); @@ -502,22 +537,25 @@ void AbstractServerSocketInterface::sendServerMessage(const QString userName, co Response::ResponseCode AbstractServerSocketInterface::cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer & /*rc*/) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } sqlInterface->checkSql(); int basePathId = getDeckPathId(nameFromStdString(cmd.path())); - if ((basePathId == -1) || (basePathId == 0)) + if ((basePathId == -1) || (basePathId == 0)) { return Response::RespNameNotFound; + } deckDelDirHelper(basePathId); return Response::RespOk; } Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer & /*rc*/) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } sqlInterface->checkSql(); QSqlQuery *query = @@ -525,8 +563,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_D query->bindValue(":id", cmd.deck_id()); query->bindValue(":id_user", userInfo->id()); sqlInterface->execSqlQuery(query); - if (!query->next()) + if (!query->next()) { return Response::RespNameNotFound; + } query = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id = :id"); query->bindValue(":id", cmd.deck_id()); @@ -538,27 +577,32 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_D Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } - if (!cmd.has_deck_list()) + if (!cmd.has_deck_list()) { return Response::RespInvalidData; + } sqlInterface->checkSql(); QString deckStr = fileFromStdString(cmd.deck_list()); DeckList deck; - if (!deck.loadFromString_Native(deckStr)) + if (!deck.loadFromString_Native(deckStr)) { return Response::RespContextError; + } QString deckName = deck.getName(); - if (deckName.isEmpty()) + if (deckName.isEmpty()) { deckName = "Unnamed deck"; + } if (cmd.has_path()) { int folderId = getDeckPathId(nameFromStdString(cmd.path())); - if (folderId == -1) + if (folderId == -1) { return Response::RespNameNotFound; + } QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_decklist_files (id_folder, id_user, name, upload_time, " @@ -585,8 +629,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman query->bindValue(":content", deckStr); sqlInterface->execSqlQuery(query); - if (query->numRowsAffected() == 0) + if (query->numRowsAffected() == 0) { return Response::RespNameNotFound; + } Response_DeckUpload *re = new Response_DeckUpload; ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); @@ -594,8 +639,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman fileInfo->set_name(deckName.toStdString()); fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch()); rc.setResponseExtension(re); - } else + } else { return Response::RespInvalidData; + } return Response::RespOk; } @@ -603,8 +649,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman Response::ResponseCode AbstractServerSocketInterface::cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } DeckList *deck; try { @@ -624,8 +671,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDownload(const Comm Response::ResponseCode AbstractServerSocketInterface::cmdReplayList(const Command_ReplayList & /*cmd*/, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } Response_ReplayList *re = new Response_ReplayList; @@ -654,8 +702,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayList(const Comman sqlInterface->prepareQuery("select player_name from {prefix}_games_players where id_game = :id_game"); query2->bindValue(":id_game", gameId); sqlInterface->execSqlQuery(query2); - while (query2->next()) + while (query2->next()) { matchInfo->add_player_names(query2->value(0).toString().toStdString()); + } } { QSqlQuery *query3 = @@ -678,8 +727,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayList(const Comman Response::ResponseCode AbstractServerSocketInterface::cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } { QSqlQuery *query = @@ -687,18 +737,22 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayDownload(const Co "a.id_game = b.id_game where b.id = :id_replay and a.id_player = :id_player"); query->bindValue(":id_replay", cmd.replay_id()); query->bindValue(":id_player", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; - if (!query->next()) + } + if (!query->next()) { return Response::RespAccessDenied; + } } QSqlQuery *query = sqlInterface->prepareQuery("select replay from {prefix}_replays where id = :id_replay"); query->bindValue(":id_replay", cmd.replay_id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; - if (!query->next()) + } + if (!query->next()) { return Response::RespNameNotFound; + } QByteArray data = query->value(0).toByteArray(); @@ -712,11 +766,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayDownload(const Co Response::ResponseCode AbstractServerSocketInterface::cmdReplayModifyMatch(const Command_ReplayModifyMatch &cmd, ResponseContainer & /*rc*/) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } - if (!sqlInterface->checkSql()) + if (!sqlInterface->checkSql()) { return Response::RespInternalError; + } QSqlQuery *query = sqlInterface->prepareQuery("update {prefix}_replays_access set do_not_hide=:do_not_hide where " "id_player = :id_player and id_game = :id_game"); @@ -724,27 +780,31 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayModifyMatch(const query->bindValue(":id_game", cmd.game_id()); query->bindValue(":do_not_hide", cmd.do_not_hide()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } return query->numRowsAffected() > 0 ? Response::RespOk : Response::RespNameNotFound; } Response::ResponseCode AbstractServerSocketInterface::cmdReplayDeleteMatch(const Command_ReplayDeleteMatch &cmd, ResponseContainer & /*rc*/) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } - if (!sqlInterface->checkSql()) + if (!sqlInterface->checkSql()) { return Response::RespInternalError; + } QSqlQuery *query = sqlInterface->prepareQuery( "delete from {prefix}_replays_access where id_player = :id_player and id_game = :id_game"); query->bindValue(":id_player", userInfo->id()); query->bindValue(":id_game", cmd.game_id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } return query->numRowsAffected() > 0 ? Response::RespOk : Response::RespNameNotFound; } @@ -764,8 +824,9 @@ QString AbstractServerSocketInterface::createHashForReplay(int gameId) sqlInterface->prepareQuery("select replay from {prefix}_replays where id_game = :id_game limit 3"); query->bindValue(":id_game", gameId); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return ""; + } QByteArray replaysBytes; while (query->next()) { @@ -783,8 +844,9 @@ QString AbstractServerSocketInterface::createHashForReplay(int gameId) Response::ResponseCode AbstractServerSocketInterface::cmdReplayGetCode(const Command_ReplayGetCode &cmd, ResponseContainer &rc) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } // Check that user has access to replay match { @@ -792,10 +854,12 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayGetCode(const Com "select 1 from {prefix}_replays_access where id_game = :id_game and id_player = :id_player"); query->bindValue(":id_game", cmd.game_id()); query->bindValue(":id_player", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; - if (!query->next()) + } + if (!query->next()) { return Response::RespAccessDenied; + } } QString hash = createHashForReplay(cmd.game_id()); @@ -894,12 +958,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com bool roomType = false; for (int i = 0; i != cmd.log_location_size(); ++i) { - if (nameFromStdString(cmd.log_location(i)).simplified() == "room") + if (nameFromStdString(cmd.log_location(i)).simplified() == "room") { roomType = true; - if (nameFromStdString(cmd.log_location(i)).simplified() == "game") + } + if (nameFromStdString(cmd.log_location(i)).simplified() == "game") { gameType = true; - if (nameFromStdString(cmd.log_location(i)).simplified() == "chat") + } + if (nameFromStdString(cmd.log_location(i)).simplified() == "chat") { chatType = true; + } } int dateRange = cmd.date_range(); @@ -910,8 +977,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com if (servatrice->getEnableLogQuery()) { QListIterator messageIterator(sqlInterface->getMessageLogHistory( userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults)); - while (messageIterator.hasNext()) + while (messageIterator.hasNext()) { re->add_log_message()->CopyFrom(messageIterator.next()); + } } else { ServerInfo_ChatMessage chatMessage; @@ -949,8 +1017,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com messageList << chatMessage; QListIterator messageIterator(messageList); - while (messageIterator.hasNext()) + while (messageIterator.hasNext()) { re->add_log_message()->CopyFrom(messageIterator.next()); + } } rc.setResponseExtension(re); @@ -965,8 +1034,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetBanHistory(const Com Response_BanHistory *re = new Response_BanHistory; QListIterator banIterator(sqlInterface->getUserBanHistory(userName)); - while (banIterator.hasNext()) + while (banIterator.hasNext()) { re->add_ban_list()->CopyFrom(banIterator.next()); + } rc.setResponseExtension(re); return Response::RespOk; } @@ -995,8 +1065,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetWarnHistory(const Co Response_WarnHistory *re = new Response_WarnHistory; QListIterator warnIterator(sqlInterface->getUserWarnHistory(userName)); - while (warnIterator.hasNext()) + while (warnIterator.hasNext()) { re->add_warn_list()->CopyFrom(warnIterator.next()); + } rc.setResponseExtension(re); return Response::RespOk; } @@ -1056,16 +1127,18 @@ Response::ResponseCode AbstractServerSocketInterface::cmdWarnUser(const Command_ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer & /*rc*/) { - if (!sqlInterface->checkSql()) + if (!sqlInterface->checkSql()) { return Response::RespInternalError; + } QString userName = nameFromStdString(cmd.user_name()).simplified(); QString address = nameFromStdString(cmd.address()).simplified(); QString clientId = nameFromStdString(cmd.clientid()).simplified(); QString visibleReason = textFromStdString(cmd.visible_reason()); - if (userName.isEmpty() && address.isEmpty() && clientId.isEmpty()) + if (userName.isEmpty() && address.isEmpty() && clientId.isEmpty()) { return Response::RespOk; + } int amountRemove = cmd.remove_messages(); if (amountRemove != 0) { @@ -1073,8 +1146,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com } QString trustedSources = settingsCache->value("server/trusted_sources", "127.0.0.1,::1").toString(); int minutes = cmd.minutes(); - if (trustedSources.contains(address, Qt::CaseInsensitive)) + if (trustedSources.contains(address, Qt::CaseInsensitive)) { address = ""; + } QSqlQuery *query = sqlInterface->prepareQuery( "insert into {prefix}_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason, " @@ -1095,8 +1169,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com if (!userName.isEmpty()) { AbstractServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (user && !userList.contains(user)) + if (user && !userList.contains(user)) { userList.append(user); + } } if (userName.isEmpty() && address.isEmpty() && (!clientId.isEmpty())) { @@ -1111,8 +1186,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com userName = clientIdQuery->value(0).toString(); AbstractServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (user && !userList.contains(user)) + if (user && !userList.contains(user)) { userList.append(user); + } } } } @@ -1121,10 +1197,12 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com if (!userList.isEmpty()) { Event_ConnectionClosed event; event.set_reason(Event_ConnectionClosed::BANNED); - if (cmd.has_visible_reason()) + if (cmd.has_visible_reason()) { event.set_reason_str(visibleReason.toStdString()); - if (minutes) + } + if (minutes) { event.set_end_time(QDateTime::currentDateTime().addSecs(60 * minutes).toSecsSinceEpoch()); + } for (int i = 0; i < userList.size(); ++i) { SessionEvent *se = userList[i]->prepareSessionEvent(event); userList[i]->sendProtocolItem(*se); @@ -1136,12 +1214,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com for (QString &moderator : moderatorList) { QString notificationMessage = QString::fromStdString(userInfo->name()).simplified() + " has placed a ban with the following information"; - if (!userName.isEmpty()) + if (!userName.isEmpty()) { notificationMessage.append("\n Username: " + userName); - if (!address.isEmpty()) + } + if (!address.isEmpty()) { notificationMessage.append("\n IP Address: " + address); - if (!clientId.isEmpty()) + } + if (!clientId.isEmpty()) { notificationMessage.append("\n Client ID: " + clientId); + } notificationMessage.append("\n Length: " + QString::number(minutes) + " minute(s)"); notificationMessage.append("\n Internal Reason: " + textFromStdString(cmd.reason())); @@ -1161,9 +1242,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); if (!registrationEnabled) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Server functionality disabled", false); + } return Response::RespRegistrationDisabled; } @@ -1196,19 +1278,21 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C // If a blacklist exists, ensure the email address domain is NOT in the blacklist if (!emailBlackListFilters.isEmpty() && emailBlackListFilters.contains(emailDomain, Qt::CaseInsensitive)) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Email used is blacklisted", false); + } return Response::RespEmailBlackListed; } - //! \todo Move this method outside of the db interface + //! \todo Move this method outside of the db interface. QString errorString; if (!sqlInterface->usernameIsValid(userName, errorString)) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Username is invalid", false); + } Response_Register *re = new Response_Register; re->set_denied_reason_str(errorString.toStdString()); @@ -1217,17 +1301,19 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C } if (userName.toLower().simplified() == "servatrice") { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Username is invalid", false); + } return Response::RespUsernameInvalid; } if (sqlInterface->userExists(userName)) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Username already exists", false); + } return Response::RespUserAlreadyExists; } @@ -1235,10 +1321,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(parsedEmailParts); if (servatrice->getMaxAccountsPerEmail() > 0 && sqlInterface->checkNumberOfUserAccounts(parsedEmailAddress) >= servatrice->getMaxAccountsPerEmail()) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Too many usernames registered with this email address", false); + } return Response::RespTooManyRequests; } @@ -1246,23 +1333,26 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C QString banReason; int banSecondsRemaining; if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, clientId, banReason, banSecondsRemaining)) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "User is banned", false); + } Response_Register *re = new Response_Register; re->set_denied_reason_str(banReason.toStdString()); - if (banSecondsRemaining != 0) + if (banSecondsRemaining != 0) { re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsRemaining).toSecsSinceEpoch()); + } rc.setResponseExtension(re); return Response::RespUserIsBanned; } if (tooManyRegistrationAttempts(this->getAddress())) { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Too many registration attempts from this ip address", false); + } return Response::RespTooManyRequests; } @@ -1272,8 +1362,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C QString password; bool passwordNeedsHash = false; if (cmd.has_password()) { - if (cmd.password().length() > MAX_NAME_LENGTH) + if (cmd.password().length() > MAX_NAME_LENGTH) { return Response::RespRegistrationFailed; + } password = QString::fromStdString(cmd.password()); passwordNeedsHash = true; if (!isPasswordLongEnough(password.length())) { @@ -1299,26 +1390,30 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_activation_emails (name) values(:name)"); query->bindValue(":name", userName); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespRegistrationFailed; + } - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "", true); + } return Response::RespRegistrationAcceptedNeedsActivation; } else { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "", true); + } return Response::RespRegistrationAccepted; } } else { - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Unknown reason for failure", false); + } return Response::RespRegistrationFailed; } @@ -1326,7 +1421,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C bool AbstractServerSocketInterface::tooManyRegistrationAttempts(const QString &ipAddress) { - //! \todo implement + //! \todo Implement registration attempt limiting. Q_UNUSED(ipAddress); return false; } @@ -1338,24 +1433,27 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C QString token = nameFromStdString(cmd.token()); QString clientId = nameFromStdString(cmd.clientid()); - if (clientId.isEmpty()) + if (clientId.isEmpty()) { clientId = "UNKNOWN"; + } if (sqlInterface->activateUser(userName, token)) { qCDebug(AbstractServerSocketInterfaceLog) << "Accepted activation for user" << userName; - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "ACTIVATE_ACCOUNT", "", true); + } return Response::RespActivationAccepted; } else { qCDebug(AbstractServerSocketInterfaceLog) << "Failed activation for user" << userName; - if (servatrice->getEnableRegistrationAudit()) + if (servatrice->getEnableRegistrationAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "ACTIVATE_ACCOUNT", "Failed to activate account, incorrect activation token", false); + } return Response::RespActivationFailed; } @@ -1364,8 +1462,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer & /* rc */) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } QString realName = nameFromStdString(cmd.real_name()); const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email())); @@ -1374,8 +1473,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma bool checkedPassword = false; QString userName = QString::fromStdString(userInfo->name()); if (cmd.has_password_check()) { - if (cmd.password_check().length() > MAX_NAME_LENGTH) + if (cmd.password_check().length() > MAX_NAME_LENGTH) { return Response::RespWrongPassword; + } QString password = QString::fromStdString(cmd.password_check()); QString clientId = QString::fromStdString(userInfo->clientid()); QString reasonStr{}; @@ -1406,8 +1506,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma queryList << "country=:country"; } - if (queryList.isEmpty()) + if (queryList.isEmpty()) { return Response::RespOk; + } QString queryText = QString("update {prefix}_users set %1 where name=:userName").arg(queryList.join(", ")); QSqlQuery *query = sqlInterface->prepareQuery(queryText); @@ -1425,8 +1526,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma } query->bindValue(":userName", userName); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } if (cmd.has_real_name()) { userInfo->set_real_name(realName.toStdString()); @@ -1444,8 +1546,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer & /* rc */) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } size_t length = qMin(cmd.image().length(), (size_t)MAX_FILE_LENGTH); QByteArray image(cmd.image().c_str(), length); @@ -1454,8 +1557,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm QSqlQuery *query = sqlInterface->prepareQuery("update {prefix}_users set avatar_bmp=:image where id=:id"); query->bindValue(":image", image); query->bindValue(":id", id); - if (!sqlInterface->execSqlQuery(query)) + if (!sqlInterface->execSqlQuery(query)) { return Response::RespInternalError; + } userInfo->set_avatar_bmp(cmd.image().c_str(), length); return Response::RespOk; @@ -1464,21 +1568,25 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm Response::ResponseCode AbstractServerSocketInterface::cmdAccountPassword(const Command_AccountPassword &cmd, ResponseContainer & /* rc */) { - if (authState != PasswordRight) + if (authState != PasswordRight) { return Response::RespFunctionNotAllowed; + } - if (cmd.old_password().length() > MAX_NAME_LENGTH) + if (cmd.old_password().length() > MAX_NAME_LENGTH) { return Response::RespWrongPassword; + } QString oldPassword = QString::fromStdString(cmd.old_password()); QString newPassword; bool newPasswordNeedsHash = false; if (cmd.has_new_password()) { - if (cmd.new_password().length() > MAX_NAME_LENGTH) + if (cmd.new_password().length() > MAX_NAME_LENGTH) { return Response::RespContextError; + } newPassword = QString::fromStdString(cmd.new_password()); newPasswordNeedsHash = true; - if (!isPasswordLongEnough(newPassword.length())) + if (!isPasswordLongEnough(newPassword.length())) { return Response::RespPasswordTooShort; + } } else if (cmd.hashed_new_password().length() > MAX_NAME_LENGTH) { return Response::RespContextError; } else { @@ -1486,8 +1594,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountPassword(const C } QString userName = QString::fromStdString(userInfo->name()); - if (!databaseInterface->changeUserPassword(userName, oldPassword, true, newPassword, newPasswordNeedsHash)) + if (!databaseInterface->changeUserPassword(userName, oldPassword, true, newPassword, newPasswordNeedsHash)) { return Response::RespWrongPassword; + } return Response::RespOk; } @@ -1501,9 +1610,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordRequest(c qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password request from user:" << userName; if (!servatrice->getEnableForgotPassword()) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET_REQUEST", "Server functionality disabled", false); + } return Response::RespFunctionNotAllowed; } @@ -1516,9 +1626,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordRequest(c } if (!sqlInterface->userExists(userName)) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET_REQUEST", "User does not exist", false); + } return Response::RespFunctionNotAllowed; } @@ -1533,9 +1644,10 @@ Response::ResponseCode AbstractServerSocketInterface::continuePasswordRequest(co { if (sqlInterface->doesForgotPasswordExist(userName)) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET_REQUEST", "Request already exists", true); + } Response_ForgotPasswordRequest *re = new Response_ForgotPasswordRequest; re->set_challenge_email(false); @@ -1546,9 +1658,10 @@ Response::ResponseCode AbstractServerSocketInterface::continuePasswordRequest(co QString banReason; int banTimeRemaining; if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, clientId, banReason, banTimeRemaining)) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET_REQUEST", "User is banned", false); + } return Response::RespFunctionNotAllowed; } @@ -1583,18 +1696,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password reset from user:" << userName; if (!sqlInterface->doesForgotPasswordExist(userName)) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET", "Request does not exist for user", false); + } return Response::RespFunctionNotAllowed; } if (!sqlInterface->validateTableColumnStringData("{prefix}_users", "token", userName, nameFromStdString(cmd.token()))) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), userName.simplified(), "PASSWORD_RESET", "Failed token validation", false); + } return Response::RespFunctionNotAllowed; } @@ -1602,8 +1717,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con QString password; bool passwordNeedsHash = false; if (cmd.has_new_password()) { - if (cmd.new_password().length() > MAX_NAME_LENGTH) + if (cmd.new_password().length() > MAX_NAME_LENGTH) { return Response::RespContextError; + } password = QString::fromStdString(cmd.new_password()); passwordNeedsHash = true; } else if (cmd.hashed_new_password().length() > MAX_NAME_LENGTH) { @@ -1613,9 +1729,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con } if (sqlInterface->changeUserPassword(nameFromStdString(cmd.user_name()), password, passwordNeedsHash)) { - if (servatrice->getEnableForgotPasswordAudit()) + if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET", "", true); + } sqlInterface->removeForgotPassword(nameFromStdString(cmd.user_name())); return Response::RespOk; @@ -1961,8 +2078,9 @@ void TcpServerSocketInterface::initSessionDeprecated() void TcpServerSocketInterface::flushOutputQueue() { QMutexLocker locker(&outputQueueMutex); - if (outputQueue.isEmpty()) + if (outputQueue.isEmpty()) { return; + } int totalBytes = 0; while (!outputQueue.isEmpty()) { @@ -2011,11 +2129,13 @@ void TcpServerSocketInterface::readClient() ((quint32)(unsigned char)inputBuffer[3]); inputBuffer.remove(0, 4); messageInProgress = true; - } else + } else { return; + } } - if (inputBuffer.size() < messageLength || messageLength < 0) + if (inputBuffer.size() < messageLength || messageLength < 0) { return; + } CommandContainer newCommandContainer; bool ok; @@ -2048,12 +2168,13 @@ void TcpServerSocketInterface::readClient() if (ok) { // dirty hack to make v13 client display the correct error message - if (handshakeStarted) + if (handshakeStarted) { processCommandContainer(newCommandContainer); - else if (!newCommandContainer.has_cmd_id()) { + } else if (!newCommandContainer.has_cmd_id()) { handshakeStarted = true; - if (!initTcpSession()) + if (!initTcpSession()) { prepareDestroy(); + } } // end of hack } else { @@ -2065,8 +2186,9 @@ void TcpServerSocketInterface::readClient() bool TcpServerSocketInterface::initTcpSession() { - if (!initSession()) + if (!initSession()) { return false; + } // limit the number of websocket users based on configuration settings bool enforceUserLimit = settingsCache->value("security/enable_max_user_limit", false).toBool(); @@ -2138,14 +2260,16 @@ void WebsocketServerSocketInterface::initConnection(void *_socket) QString("Incoming websocket connection: %1 (%2)").arg(address.toString()).arg(socket->peerAddress().toString()), this); - if (!initWebsocketSession()) + if (!initWebsocketSession()) { prepareDestroy(); + } } bool WebsocketServerSocketInterface::initWebsocketSession() { - if (!initSession()) + if (!initSession()) { return false; + } // limit the number of websocket users based on configuration settings bool enforceUserLimit = settingsCache->value("security/enable_max_user_limit", false).toBool(); @@ -2172,8 +2296,9 @@ bool WebsocketServerSocketInterface::initWebsocketSession() void WebsocketServerSocketInterface::flushOutputQueue() { QMutexLocker locker(&outputQueueMutex); - if (outputQueue.isEmpty()) + if (outputQueue.isEmpty()) { return; + } qint64 totalBytes = 0; while (!outputQueue.isEmpty()) { diff --git a/servatrice/src/settingscache.cpp b/servatrice/src/settingscache.cpp index f6dcd5fc8..f6c862904 100644 --- a/servatrice/src/settingscache.cpp +++ b/servatrice/src/settingscache.cpp @@ -30,14 +30,16 @@ QString SettingsCache::guessConfigurationPath() // application directory path guessFileName = QCoreApplication::applicationDirPath() + "/" + fileName; - if (QFile::exists(guessFileName)) + if (QFile::exists(guessFileName)) { return guessFileName; + } #ifdef Q_OS_UNIX // /etc guessFileName = "/etc/servatrice/" + fileName; - if (QFile::exists(guessFileName)) + if (QFile::exists(guessFileName)) { return guessFileName; + } #endif guessFileName = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + fileName; diff --git a/servatrice/src/signalhandler.cpp b/servatrice/src/signalhandler.cpp index f7cb207a9..b4513979f 100644 --- a/servatrice/src/signalhandler.cpp +++ b/servatrice/src/signalhandler.cpp @@ -86,10 +86,11 @@ void SignalHandler::sigSegvHandler(int sig) fprintf(stderr, "Error: signal %d:\n", sig); backtrace_symbols_fd(array, size, STDERR_FILENO); - if (sig == SIGSEGV) + if (sig == SIGSEGV) { logger->logMessage("CRASH: SIGSEGV"); - else if (sig == SIGABRT) + } else if (sig == SIGABRT) { logger->logMessage("CRASH: SIGABRT"); + } logger->deleteLater(); loggerThread->wait(); diff --git a/servatrice/src/smtpclient.cpp b/servatrice/src/smtpclient.cpp index ee7214904..3ec333413 100644 --- a/servatrice/src/smtpclient.cpp +++ b/servatrice/src/smtpclient.cpp @@ -125,11 +125,13 @@ bool SmtpClient::enqueueForgotPasswordTokenMail(const QString &nickname, const Q void SmtpClient::sendAllEmails() { // still connected from the previous round - if (smtp->socket()->state() == QAbstractSocket::ConnectedState) + if (smtp->socket()->state() == QAbstractSocket::ConnectedState) { return; + } - if (smtp->pendingMessages() == 0) + if (smtp->pendingMessages() == 0) { return; + } QString connectionType = settingsCache->value("smtp/connection", "tcp").toString(); QString host = settingsCache->value("smtp/host", "localhost").toString(); @@ -143,8 +145,9 @@ void SmtpClient::sendAllEmails() // Connect if (connectionType == "ssl") { - if (acceptAllCerts) + if (acceptAllCerts) { smtp->sslSocket()->setPeerVerifyMode(QSslSocket::QueryPeer); + } smtp->connectToSecureHost(host, port); } else { smtp->connectToHost(host, port); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5346e59f..04ac7fcee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,8 @@ add_test(NAME dummy_test COMMAND dummy_test) add_test(NAME expression_test COMMAND expression_test) add_test(NAME test_age_formatting COMMAND test_age_formatting) add_test(NAME password_hash_test COMMAND password_hash_test) +add_test(NAME server_card_counter_test COMMAND server_card_counter_test) +add_test(NAME server_counter_test COMMAND server_counter_test) add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test) set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5) @@ -17,6 +19,8 @@ add_executable(expression_test expression_test.cpp) add_executable(test_age_formatting test_age_formatting.cpp) add_executable(password_hash_test password_hash_test.cpp) add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) +add_executable(server_card_counter_test server_card_counter_test.cpp) +add_executable(server_counter_test server_counter_test.cpp) find_package(GTest) @@ -48,12 +52,16 @@ if(NOT GTEST_FOUND) add_dependencies(test_age_formatting gtest) add_dependencies(password_hash_test gtest) add_dependencies(deck_hash_performance_test gtest) + add_dependencies(server_card_counter_test gtest) + add_dependencies(server_counter_test gtest) endif() include_directories(${GTEST_INCLUDE_DIRS}) target_link_libraries(dummy_test Threads::Threads ${GTEST_BOTH_LIBRARIES}) target_link_libraries(expression_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) -target_link_libraries(test_age_formatting Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) +target_link_libraries( + test_age_formatting libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) target_link_libraries( password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) @@ -61,8 +69,15 @@ target_link_libraries( deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) +target_link_libraries( + server_card_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + server_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) add_subdirectory(card_zone_algorithms) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) +add_subdirectory(movecard_tests) add_subdirectory(oracle) diff --git a/tests/card_zone_algorithms/CMakeLists.txt b/tests/card_zone_algorithms/CMakeLists.txt index 889e92eaa..e03d8f77d 100644 --- a/tests/card_zone_algorithms/CMakeLists.txt +++ b/tests/card_zone_algorithms/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(card_zone_algorithms_test card_zone_algorithms_test.cpp) -target_include_directories(card_zone_algorithms_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones/logic) +target_include_directories(card_zone_algorithms_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones) target_link_libraries( card_zone_algorithms_test diff --git a/tests/movecard_tests/CMakeLists.txt b/tests/movecard_tests/CMakeLists.txt new file mode 100755 index 000000000..769047148 --- /dev/null +++ b/tests/movecard_tests/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(reverse_card_move_test reverse_card_move_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(reverse_card_move_test gtest) +endif() + +target_link_libraries( + reverse_card_move_test + PRIVATE libcockatrice_network_server_remote + PRIVATE libcockatrice_rng + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} +) + +add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test) diff --git a/tests/movecard_tests/reverse_card_move_test.cpp b/tests/movecard_tests/reverse_card_move_test.cpp new file mode 100644 index 000000000..2231a7e3b --- /dev/null +++ b/tests/movecard_tests/reverse_card_move_test.cpp @@ -0,0 +1,91 @@ +#include "game/server_abstract_player.h" +#include "game/server_card.h" +#include "game/server_cardzone.h" +#include "game/server_game.h" +#include "server_response_containers.h" +#include "server_room.h" +#include "server_test_helpers.h" + +#include +#include +#include +#include +#include + +RNG_Abstract *rng = nullptr; // this needs to be defined due to other functions in server + +TEST(ReverseCardMoveTest, MoveCardFromBottomTest) +{ + ServerInfo_User user; + user.set_name("test-user"); + + // instantiate a fake server instance + FakeServer server; + Server_Room room(0, 0, "", "", "", "", false, "", {}, &server); + Server_Game game(user, 1, "", "", 2, QList(), false, false, false, false, false, false, 20, false, &room); + Server_AbstractPlayer player(&game, 1, user, false, nullptr); + Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone); + Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone); + + // setup the deck with 20 useless cards + for (int i = 0; i < 20; i++) { + auto *cardUseless = new Server_Card({"Card Useless", "card-Useless"}, player.newCardId(), i, 0); + deckZone.insertCard(cardUseless, i, 0); + } + + // add 4 cards to the end of it + auto *cardA = new Server_Card({"Card A", "card-a"}, player.newCardId(), 20, 0); + auto *cardB = new Server_Card({"Card B", "card-b"}, player.newCardId(), 21, 0); + auto *cardC = new Server_Card({"Card C", "card-c"}, player.newCardId(), 22, 0); + auto *cardD = new Server_Card({"Card D", "card-d"}, player.newCardId(), 23, 0); + + deckZone.insertCard(cardA, 20, 0); + deckZone.insertCard(cardB, 21, 0); + deckZone.insertCard(cardC, 22, 0); + deckZone.insertCard(cardD, 23, 0); + + // try to move them, with the expected client given order (n-3, n-2, n-1, n) + CardToMove moveA; + moveA.set_card_id(cardA->getId()); + CardToMove moveB; + moveB.set_card_id(cardB->getId()); + CardToMove moveC; + moveC.set_card_id(cardC->getId()); + CardToMove moveD; + moveD.set_card_id(cardD->getId()); + + QList cardsToMove = {&moveA, &moveB, &moveC, &moveD}; + GameEventStorage ges; + + const auto response = player.moveCard(ges, &deckZone, cardsToMove, &exileZone, 0, 0, false, false, false); + + EXPECT_EQ(response, Response::RespOk); + + int positionA; + int positionB; + int positionC; + int positionD; + // find the cards in the destination zone and check they are the right card + EXPECT_EQ(exileZone.getCard(cardA->getId(), &positionA), cardA); + EXPECT_EQ(exileZone.getCard(cardB->getId(), &positionB), cardB); + EXPECT_EQ(exileZone.getCard(cardC->getId(), &positionC), cardC); + EXPECT_EQ(exileZone.getCard(cardD->getId(), &positionD), cardD); + + // check that they are at the expected index + EXPECT_EQ(cardA->getX(), 3); + EXPECT_EQ(cardB->getX(), 2); + EXPECT_EQ(cardC->getX(), 1); + EXPECT_EQ(cardD->getX(), 0); + + // also check if the given positions are correct + EXPECT_EQ(positionA, 3); + EXPECT_EQ(positionB, 2); + EXPECT_EQ(positionC, 1); + EXPECT_EQ(positionD, 0); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/movecard_tests/server_test_helpers.h b/tests/movecard_tests/server_test_helpers.h new file mode 100644 index 000000000..fd2ed6c17 --- /dev/null +++ b/tests/movecard_tests/server_test_helpers.h @@ -0,0 +1,42 @@ +#include "server.h" +#include "server_database_interface.h" + +class MockDatabaseInterface : public Server_DatabaseInterface +{ +public: + AuthenticationResult checkUserPassword(Server_ProtocolHandler *, + const QString &, + const QString &, + const QString &, + QString &, + int &, + bool) override + { + return NotLoggedIn; + } + ServerInfo_User getUserData(const QString &, bool) override + { + return ServerInfo_User(); + } + int getNextGameId() override + { + return 1; + } + int getNextReplayId() override + { + return 1; + } + int getActiveUserCount(QString) override + { + return 1; + } +}; + +class FakeServer : public Server +{ +public: + FakeServer() + { + setDatabaseInterface(new MockDatabaseInterface()); + } +}; diff --git a/tests/server_card_counter_test.cpp b/tests/server_card_counter_test.cpp new file mode 100644 index 000000000..ff906b906 --- /dev/null +++ b/tests/server_card_counter_test.cpp @@ -0,0 +1,183 @@ +/** @file server_card_counter_test.cpp + * @brief Tests for Server_Card counter operations. + * @ingroup Tests + */ + +#include +#include +#include +#include +#include +#include + +TEST(ServerCardCounter, IncrementNewCounter) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + EXPECT_TRUE(card.incrementCounter(1, 10)); + EXPECT_EQ(card.getCounter(1), 10); +} + +TEST(ServerCardCounter, IncrementExistingCounter) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + EXPECT_TRUE(card.incrementCounter(1, 10)); + EXPECT_EQ(card.getCounter(1), 60); +} + +TEST(ServerCardCounter, IncrementOverflowProtection) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD)); + EXPECT_FALSE(card.incrementCounter(1, 1)); + EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD); +} + +TEST(ServerCardCounter, DecrementUnderflowProtection) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 5)); + EXPECT_TRUE(card.incrementCounter(1, -10)); + EXPECT_EQ(card.getCounter(1), 0); + EXPECT_FALSE(card.getCounters().contains(1)); +} + +TEST(ServerCardCounter, ReturnsFalseWhenUnchanged) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + EXPECT_FALSE(card.incrementCounter(1, 0)); + EXPECT_EQ(card.getCounter(1), 50); +} + +TEST(ServerCardCounter, DecrementToZeroRemovesCounter) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 10)); + EXPECT_TRUE(card.incrementCounter(1, -10)); + EXPECT_EQ(card.getCounter(1), 0); + EXPECT_FALSE(card.getCounters().contains(1)); +} + +TEST(ServerCardCounter, SetToZeroRemovesCounter) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 10)); + EXPECT_TRUE(card.setCounter(1, 0)); + EXPECT_EQ(card.getCounter(1), 0); + EXPECT_FALSE(card.getCounters().contains(1)); +} + +TEST(ServerCardCounter, SetCounterReturnsFalseWhenUnchanged) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + EXPECT_FALSE(card.setCounter(1, 50)); + EXPECT_EQ(card.getCounter(1), 50); +} + +TEST(ServerCardCounter, SetCounterReturnsTrueWhenChanged) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + EXPECT_TRUE(card.setCounter(1, 100)); + EXPECT_EQ(card.getCounter(1), 100); +} + +TEST(ServerCardCounter, SetCounterEventNotPopulatedWhenUnchanged) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + + Event_SetCardCounter event; + event.set_counter_id(999); + event.set_counter_value(999); + + EXPECT_FALSE(card.setCounter(1, 50, &event)); + EXPECT_EQ(event.counter_id(), 999); + EXPECT_EQ(event.counter_value(), 999); +} + +TEST(ServerCardCounter, IncrementCounterPopulatesEvent) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + + Event_SetCardCounter event; + EXPECT_TRUE(card.incrementCounter(1, 10, &event)); + + EXPECT_EQ(event.counter_id(), 1); + EXPECT_EQ(event.counter_value(), 60); +} + +TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5)); + + Event_SetCardCounter event; + EXPECT_TRUE(card.incrementCounter(1, 10, &event)); + + EXPECT_EQ(event.counter_id(), 1); + EXPECT_EQ(event.counter_value(), MAX_COUNTERS_ON_CARD); +} + +TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 50)); + EXPECT_TRUE(card.incrementCounter(1, 10, nullptr)); + EXPECT_EQ(card.getCounter(1), 60); +} + +TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD)); + + Event_SetCardCounter event; + event.set_counter_id(999); + event.set_counter_value(999); + + EXPECT_FALSE(card.incrementCounter(1, 1, &event)); + EXPECT_EQ(event.counter_id(), 999); + EXPECT_EQ(event.counter_value(), 999); +} + +TEST(ServerCardCounter, SetCounterClampsNegativeToZero) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + EXPECT_FALSE(card.setCounter(1, -5)); + EXPECT_EQ(card.getCounter(1), 0); + EXPECT_FALSE(card.getCounters().contains(1)); +} + +TEST(ServerCardCounter, SetCounterClampsAboveMaxToMax) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + EXPECT_TRUE(card.setCounter(1, 1500)); + EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD); +} + +TEST(ServerCardCounter, IncrementDoesNotGoBelowZero) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, 5)); + EXPECT_TRUE(card.incrementCounter(1, -10)); + EXPECT_EQ(card.getCounter(1), 0); + EXPECT_FALSE(card.getCounters().contains(1)); +} + +TEST(ServerCardCounter, IncrementDoesNotExceedMax) +{ + Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); + ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5)); + EXPECT_TRUE(card.incrementCounter(1, 10)); + EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/server_counter_test.cpp b/tests/server_counter_test.cpp new file mode 100644 index 000000000..0f41f2cbd --- /dev/null +++ b/tests/server_counter_test.cpp @@ -0,0 +1,86 @@ +/** @file server_counter_test.cpp + * @brief Tests for Server_Counter operations. + * @ingroup Tests + */ + +#include +#include +#include + +TEST(ServerCounter, IncrementDoesNotOverflow) +{ + Server_Counter c(1, "test", color(), 10, std::numeric_limits::max()); + bool changed = c.incrementCount(1); + EXPECT_FALSE(changed); + EXPECT_EQ(c.getCount(), std::numeric_limits::max()); +} + +TEST(ServerCounter, DecrementDoesNotUnderflow) +{ + Server_Counter c(1, "test", color(), 10, std::numeric_limits::min()); + bool changed = c.incrementCount(-1); + EXPECT_FALSE(changed); + EXPECT_EQ(c.getCount(), std::numeric_limits::min()); +} + +TEST(ServerCounter, SetCountReturnsFalseWhenUnchanged) +{ + Server_Counter c(1, "test", color(), 10, 50); + bool changed = c.setCount(50); + EXPECT_FALSE(changed); +} + +TEST(ServerCounter, IncrementReturnsChangeStatus) +{ + Server_Counter c(1, "test", color(), 10, 50); + EXPECT_TRUE(c.incrementCount(10)); + EXPECT_EQ(c.getCount(), 60); + EXPECT_FALSE(c.incrementCount(0)); + EXPECT_EQ(c.getCount(), 60); +} + +TEST(ServerCounter, LargePositiveDeltaDoesNotOverflow) +{ + Server_Counter c(1, "test", color(), 10, std::numeric_limits::max() - 10); + bool changed = c.incrementCount(std::numeric_limits::max()); + EXPECT_TRUE(changed); // Value changes from INT_MAX-10 to INT_MAX (clamped) + EXPECT_EQ(c.getCount(), std::numeric_limits::max()); +} + +TEST(ServerCounter, LargeNegativeDeltaDoesNotUnderflow) +{ + Server_Counter c(1, "test", color(), 10, std::numeric_limits::min() + 10); + bool changed = c.incrementCount(std::numeric_limits::min()); + EXPECT_TRUE(changed); // Value changes from INT_MIN+10 to INT_MIN (clamped) + EXPECT_EQ(c.getCount(), std::numeric_limits::min()); +} + +TEST(ServerCounter, SetCountReturnsTrueWhenChanged) +{ + Server_Counter c(1, "test", color(), 10, 50); + EXPECT_TRUE(c.setCount(100)); + EXPECT_EQ(c.getCount(), 100); +} + +TEST(ServerCounter, BasicIncrementWorks) +{ + Server_Counter c(1, "test", color(), 10, 50); + EXPECT_TRUE(c.incrementCount(10)); + EXPECT_EQ(c.getCount(), 60); + EXPECT_TRUE(c.incrementCount(-20)); + EXPECT_EQ(c.getCount(), 40); +} + +TEST(ServerCounter, MixedExtremesDoNotClamp) +{ + Server_Counter c(1, "test", color(), 10, std::numeric_limits::max()); + bool changed = c.incrementCount(std::numeric_limits::min()); + EXPECT_TRUE(changed); + EXPECT_EQ(c.getCount(), -1); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/test_age_formatting.cpp b/tests/test_age_formatting.cpp index e4fc64cf9..6a9d5d4af 100644 --- a/tests/test_age_formatting.cpp +++ b/tests/test_age_formatting.cpp @@ -1,6 +1,5 @@ -#include "../cockatrice/src/interface/widgets/server/user/user_info_box.h" - #include "gtest/gtest.h" +#include namespace { @@ -8,31 +7,31 @@ using dayyear = QPair; TEST(AgeFormatting, Zero) { - auto got = UserInfoBox::getDaysAndYearsBetween(QDate(2000, 1, 1), QDate(2000, 1, 1)); + auto got = getDaysAndYearsBetween(QDate(2000, 1, 1), QDate(2000, 1, 1)); ASSERT_EQ(got, dayyear(0, 0)) << "these are the same day"; } TEST(AgeFormatting, LeapDay) { - auto got = UserInfoBox::getDaysAndYearsBetween(QDate(2000, 2, 28), QDate(2000, 3, 1)); + auto got = getDaysAndYearsBetween(QDate(2000, 2, 28), QDate(2000, 3, 1)); ASSERT_EQ(got, dayyear(2, 0)) << "there is a leap day in between these days"; } TEST(AgeFormatting, LeapYear) { - auto got = UserInfoBox::getDaysAndYearsBetween(QDate(2000, 1, 1), QDate(2001, 1, 1)); + auto got = getDaysAndYearsBetween(QDate(2000, 1, 1), QDate(2001, 1, 1)); ASSERT_EQ(got, dayyear(0, 1)) << "there is a leap day in between these dates, but that's fine"; } TEST(AgeFormatting, LeapDayWithYear) { - auto got = UserInfoBox::getDaysAndYearsBetween(QDate(2000, 2, 28), QDate(2001, 3, 1)); + auto got = getDaysAndYearsBetween(QDate(2000, 2, 28), QDate(2001, 3, 1)); ASSERT_EQ(got, dayyear(1, 1)) << "there is a leap day in between these days but not in the last year"; } TEST(AgeFormatting, LeapDayThisYear) { - auto got = UserInfoBox::getDaysAndYearsBetween(QDate(2003, 2, 28), QDate(2004, 3, 1)); + auto got = getDaysAndYearsBetween(QDate(2003, 2, 28), QDate(2004, 3, 1)); ASSERT_EQ(got, dayyear(2, 1)) << "there is a leap day in between these days this year"; } } // namespace diff --git a/vcpkg b/vcpkg index 74e653621..56bb24116 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 74e6536215718009aae747d86d84b78376bf9e09 +Subproject commit 56bb2411609227288b70117ead2c47585ba07713 diff --git a/webclient/.env b/webclient/.env deleted file mode 100644 index 8592b28b4..000000000 --- a/webclient/.env +++ /dev/null @@ -1 +0,0 @@ -# Future template for server admin configuration diff --git a/webclient/.env.development b/webclient/.env.development deleted file mode 100644 index 2accbba81..000000000 --- a/webclient/.env.development +++ /dev/null @@ -1 +0,0 @@ -ESLINT_NO_DEV_ERRORS=true diff --git a/webclient/.env.production b/webclient/.env.production deleted file mode 100644 index 02269f00d..000000000 --- a/webclient/.env.production +++ /dev/null @@ -1 +0,0 @@ -DISABLE_ESLINT_PLUGIN=true diff --git a/webclient/.env.test b/webclient/.env.test deleted file mode 100644 index 8711f95ab..000000000 --- a/webclient/.env.test +++ /dev/null @@ -1 +0,0 @@ -CI=true diff --git a/webclient/.eslintrc.js b/webclient/.eslintrc.js deleted file mode 100644 index 98ce44430..000000000 --- a/webclient/.eslintrc.js +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = { - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": {"project": ["./tsconfig.json"]}, - "plugins": [ - "@typescript-eslint" - ], - "ignorePatterns": ["node_modules/*", "build/*", "public/pb/*"], - "env": { - "jest": true - }, - "rules": { - "array-bracket-spacing": ["error", "never"], - "arrow-spacing": ["error", {"before": true, "after": true}], - "block-spacing": ["error", "always"], - "brace-style": ["error", "1tbs", {"allowSingleLine": false}], - "comma-spacing": ["error", {"before": false, "after": true}], - "comma-style": ["error", "last"], - "computed-property-spacing": ["error", "never"], - "curly": ["error", "all"], - "dot-location": ["error", "property"], - "eol-last": ["error"], - "func-names": ["warn"], - "indent": ["error", 2, {"SwitchCase": 1}], - "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], - "keyword-spacing": ["error"], - "linebreak-style": ["error", (process.platform === "win32" ? "windows" : "unix")], - "max-len": ["error", {"code": 140}], - "no-eq-null": ["off"], - "no-func-assign": ["error"], - "no-inline-comments": ["error"], - "no-mixed-spaces-and-tabs": ["error"], - "no-multi-spaces": ["error"], - "no-spaced-func": ["error"], - "no-trailing-spaces": ["error"], - "no-var": ["error"], - "object-curly-spacing": ["error", "always"], - "one-var": ["error", "never"], - "one-var-declaration-per-line": ["error"], - "quotes": ["error", "single"], - "semi-spacing": ["error", {"before": false, "after": true}], - "space-before-blocks": ["error"], - "space-before-function-paren": ["error", {"asyncArrow": "always", "anonymous": "never", "named": "never"}], - "space-in-parens": ["error", "never"], - "space-infix-ops": ["error"], - "space-unary-ops": ["error", {"words": true, "nonwords": false}] - } -} \ No newline at end of file diff --git a/webclient/.gitignore b/webclient/.gitignore deleted file mode 100644 index 2b30e2c8d..000000000 --- a/webclient/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# generated ./src files -/src/proto-files.json -/src/server-props.json - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/webclient/.npmrc b/webclient/.npmrc deleted file mode 100644 index 521a9f7c0..000000000 --- a/webclient/.npmrc +++ /dev/null @@ -1 +0,0 @@ -legacy-peer-deps=true diff --git a/webclient/README.md b/webclient/README.md deleted file mode 100644 index 436ab4fad..000000000 --- a/webclient/README.md +++ /dev/null @@ -1,73 +0,0 @@ -## Application Architecture -![Application Architecture](architecture.png?raw=true "Application Architecture") - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -## To-Do List - -1) RefreshGuard modal - - there is no browser support for displaying custom output to window.onbeforeunload - - we should also display a custom modal explaining why they shouldnt refresh or navigate from the site - - ideally, the custom popup can be synced with the alert, so when the alert is closed, the modal closes too - -2) Disable AutoScrollToBottom when the user has scrolled up - - when the user scrolls back to bottom, it should renable - - renable after a period of inactivity (3 minutes?) - -3) Figure out how to type components w/ RouteComponentProps - - Component> - -4) clear input onSubmit - -5) figure out how to reflect server status changes in the ui - -6) Account page - -7) Register/Reset Password forms - -8) Message User - -9) Main Nav scheme diff --git a/webclient/architecture.png b/webclient/architecture.png deleted file mode 100644 index 0226eb201..000000000 Binary files a/webclient/architecture.png and /dev/null differ diff --git a/webclient/package-lock.json b/webclient/package-lock.json deleted file mode 100644 index 6b12b4ad6..000000000 --- a/webclient/package-lock.json +++ /dev/null @@ -1,34786 +0,0 @@ -{ - "name": "webclient", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "webclient", - "version": "1.0.0", - "dependencies": { - "@emotion/react": "^11.8.2", - "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^5.5.1", - "@mui/material": "^5.5.1", - "crypto-js": "^4.2.0", - "dexie": "^3.2.2", - "final-form": "^4.20.6", - "final-form-set-field-touched": "^1.0.1", - "i18next": "^22.0.4", - "i18next-browser-languagedetector": "^7.0.0", - "i18next-icu": "^2.0.3", - "intl-messageformat": "^10.2.1", - "lodash": "^4.17.21", - "prop-types": "^15.8.1", - "protobufjs": "^7.2.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-final-form": "^6.5.8", - "react-final-form-listeners": "^1.0.3", - "react-i18next": "^12.0.0", - "react-redux": "^8.0.4", - "react-router-dom": "^6.2.2", - "react-scripts": "5.0.1", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6", - "redux": "^4.1.2", - "redux-form": "^8.3.8", - "redux-thunk": "^2.4.1", - "rxjs": "^7.5.4", - "sanitize-html": "^2.7.3" - }, - "devDependencies": { - "@babel/core": "^7.17.5", - "@mui/types": "^7.1.3", - "@testing-library/jest-dom": "^5.16.2", - "@testing-library/react": "^13.4.0", - "@types/jest": "29.2.0", - "@types/jquery": "^3.5.14", - "@types/lodash": "^4.14.179", - "@types/node": "18.11.7", - "@types/prop-types": "^15.7.4", - "@types/react": "18.0.24", - "@types/react-dom": "18.0.8", - "@types/react-redux": "^7.1.23", - "@types/react-router-dom": "^5.3.3", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@types/redux-form": "^8.3.3", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", - "fs-extra": "^10.0.1", - "husky": "^8.0.1", - "typescript": "^4.6.2" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", - "dev": true - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/@babel/code-frame": { - "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/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.0.tgz", - "integrity": "sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/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==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", - "dependencies": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", - "dependencies": { - "@babel/types": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "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.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" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "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.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "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" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.0.tgz", - "integrity": "sha512-vnuRRS20ygSxclEYikHzVrP9nZDFXaSzvJxGLQNAiBX041TmhS4hOUHWNIpq/q4muENuEP9XPJFXTNFejhemkg==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.19.1", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", - "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", - "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.18.12.tgz", - "integrity": "sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz", - "integrity": "sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "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.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.30.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "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.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "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.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" - }, - "node_modules/@csstools/normalize.css": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", - "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", - "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2", - "postcss-selector-parser": "^6.0.10" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", - "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.17.12", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.1.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", - "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "dependencies": { - "@emotion/memoize": "^0.8.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "node_modules/@emotion/react": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", - "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", - "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" - }, - "node_modules/@emotion/styled": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", - "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.13.0.tgz", - "integrity": "sha512-CQ8Ykd51jYD1n05dtoX6ns6B9n/+6ZAxnWUAonvHC4kkuAemROYBhHkEB4tm1uVrRlE7gLDqXkAnY51Y0pRCWQ==", - "dependencies": { - "@formatjs/intl-localematcher": "0.2.31", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.6.tgz", - "integrity": "sha512-9CWZ3+wCkClKHX+i5j+NyoBVqGf0pIskTo6Xl6ihGokYM2yqSSS68JIgeo+99UIHc+7vi9L3/SDSz/dWI9SNlA==", - "dependencies": { - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.10.tgz", - "integrity": "sha512-KkRMxhifWkRC45dhM9tqm0GXbb6NPYTGVYY3xx891IKc6p++DQrZTnmkVSNNO47OEERLfuP2KkPFPJBuu8z/wg==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/icu-skeleton-parser": "1.3.14", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.14.tgz", - "integrity": "sha512-7bv60HQQcBb3+TSj+45tOb/CHV5z1hOpwdtS50jsSBXfB+YpGhnoRsZxSRksXeCxMy6xn6tA6VY2601BrrK+OA==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.31.tgz", - "integrity": "sha512-9QTjdSBpQ7wHShZgsNzNig5qT3rCPvmZogS/wXZzKotns5skbXgs0I7J8cuN0PPqXyynvNVuN+iOKhNS2eb+ZA==", - "dependencies": { - "tslib": "2.4.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.2.tgz", - "integrity": "sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/globals/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "dependencies": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "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.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.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "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.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "node_modules/@jridgewell/trace-mapping": { - "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" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" - }, - "node_modules/@mui/base": { - "version": "5.0.0-alpha.103", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", - "integrity": "sha512-fJIyB2df3CHn7D26WHnutnY7vew6aytTlhmRJz6GX7ag19zU2GcOUhJAzY5qwWcrXKnlYgzimhEjaEnuiUWU4g==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.11.tgz", - "integrity": "sha512-u5ff+UCFDHcR8MoQ8tuJR4c35vt7T/ki3aMEE2O3XQoGs8KJSrBiisFpFKyldg9/W2NSyoZxN+kxEGIfRxh+9Q==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - } - }, - "node_modules/@mui/icons-material": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.10.9.tgz", - "integrity": "sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==", - "dependencies": { - "@babel/runtime": "^7.19.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.11.tgz", - "integrity": "sha512-KJ0wPCTbv6sFzwA3dgg0gowdfF+SRl7D510J9l6Nl/KFX0EawcewQudqKY4slYGFXniKa5PykqokpaWXsCCPqg==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/base": "5.0.0-alpha.103", - "@mui/core-downloads-tracker": "^5.10.11", - "@mui/system": "^5.10.10", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.10.9.tgz", - "integrity": "sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/utils": "^5.10.9", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.10.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz", - "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@emotion/cache": "^11.10.3", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.10.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.10.tgz", - "integrity": "sha512-TXwtKN0adKpBrZmO+eilQWoPf2veh050HLYrN78Kps9OhlvO70v/2Kya0+mORFhu9yhpAwjHXO8JII/R4a5ZLA==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/private-theming": "^5.10.9", - "@mui/styled-engine": "^5.10.8", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.0.tgz", - "integrity": "sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==", - "peerDependencies": { - "@types/react": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.9.tgz", - "integrity": "sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.8.tgz", - "integrity": "sha512-wxXRwf+IQ6zvHSJZ+5T2RQNEsq+kx4jKRXfFvdt3nBIUzJUAvXEFsUeoaohDe/Kr84MTjGwcuIUPNcstNJORsA==", - "dependencies": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "@types/webpack": "4.x || 5.x", - "react-refresh": ">=0.10.0 <1.0.0", - "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <4.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x", - "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" - }, - "peerDependenciesMeta": { - "@types/webpack": { - "optional": true - }, - "sockjs-client": { - "optional": true - }, - "type-fest": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - }, - "webpack-hot-middleware": { - "optional": true - }, - "webpack-plugin-serve": { - "optional": true - } - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@remix-run/router": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", - "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "dependencies": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "dependencies": { - "@babel/types": "^7.12.6" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "dependencies": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@testing-library/dom": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", - "integrity": "sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", - "dev": true, - "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "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.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" - }, - "node_modules/@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", - "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jquery": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", - "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", - "dev": true, - "dependencies": { - "@types/sizzle": "*" - } - }, - "node_modules/@types/json-schema": { - "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", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" - }, - "node_modules/@types/lodash": { - "version": "4.14.186", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", - "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "node_modules/@types/node": { - "version": "18.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/react": { - "version": "18.0.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", - "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.0.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", - "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", - "dev": true, - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-virtualized-auto-sizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz", - "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-window": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz", - "integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/redux-form": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@types/redux-form/-/redux-form-8.3.5.tgz", - "integrity": "sha512-SchB4i7nxgWNbJS4cXEZducztkvHzVrb5xlAXwfLpbrLPo6tMY06+kx1GqMv42+YnGy9TpCAkF51a21HatqWBA==", - "dev": true, - "dependencies": { - "@types/react": "*", - "redux": "^3.6.0 || ^4.0.0" - } - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dev": true, - "dependencies": { - "@types/jest": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", - "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.41.0.tgz", - "integrity": "sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==", - "dependencies": { - "@typescript-eslint/utils": "5.41.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", - "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", - "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", - "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", - "dependencies": { - "@typescript-eslint/types": "5.41.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "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.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "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.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.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.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.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "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.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.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.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.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.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.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.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.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dependencies": { - "@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.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.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.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.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.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.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-node/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", - "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/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/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.2.tgz", - "integrity": "sha512-JWydkr9MirMg2jGJstDqDgzoHqaFbv7n1ghfXYdtEgXWgdq3jz7IU3SQvtj9k3mAszQBiTpQhFdlH+JIRuGTzg==", - "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.13", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", - "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001426", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axe-core": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.0.tgz", - "integrity": "sha512-4+rr8eQ7+XXS5nZrKcMO/AikHL0hVqy+lHWAnE3xdHl+aguag8SOQ6eEqLexwLNWgXIMfunGuD3ON1/6Kyet0A==", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" - }, - "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "peerDependencies": { - "@babel/core": "^7.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-react-app": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", - "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" - }, - "node_modules/bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", - "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/bonjour-service": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", - "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "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.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/check-types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", - "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" - }, - "node_modules/clean-css": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", - "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", - "dependencies": { - "browserslist": "^4.21.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "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", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-blank-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-has-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "dependencies": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/css-minimizer-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/css-minimizer-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/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "bin": { - "css-prefers-color-scheme": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "node_modules/cssdb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.2.tgz", - "integrity": "sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz", - "integrity": "sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==", - "dependencies": { - "cssnano-preset-default": "^5.2.13", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", - "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.3", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.1", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" - }, - "node_modules/deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port-alt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "dependencies": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/dexie": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", - "integrity": "sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, - "node_modules/diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, - "node_modules/dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", - "dev": true - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", - "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", - "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-flowtype": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", - "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", - "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@babel/plugin-syntax-flow": "^7.14.5", - "@babel/plugin-transform-react-jsx": "^7.14.9", - "eslint": "^8.1.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^5.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dependencies": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", - "dependencies": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-testing-library": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz", - "integrity": "sha512-6BQp3tmb79jLLasPHJmy8DnxREe+2Pgf7L+7o09TSWPfdqqtQfRZmZNetr5mOs3yqZk/MRNxpN3RUpJe0wB4LQ==", - "dependencies": { - "@typescript-eslint/utils": "^5.13.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0", - "npm": ">=6" - }, - "peerDependencies": { - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", - "dependencies": { - "@types/eslint": "^7.29.0 || ^8.4.1", - "jest-worker": "^28.0.2", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0", - "webpack": "^5.0.0" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint-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/eslint-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/eslint-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/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.2.tgz", - "integrity": "sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.2.2", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.2", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "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", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/final-form": { - "version": "4.20.7", - "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.7.tgz", - "integrity": "sha512-ii3X9wNfyBYFnDPunYN5jh1/HAvtOZ9aJI/TVk0MB86hZuOeYkb+W5L3icgwW9WWNztZR6MDU3En6eoZTUoFPg==", - "dependencies": { - "@babel/runtime": "^7.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/final-form" - } - }, - "node_modules/final-form-set-field-touched": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/final-form-set-field-touched/-/final-form-set-field-touched-1.0.1.tgz", - "integrity": "sha512-yvE5AAs9U3OgJQ9YF8NhSF0I0mJEECvOpkaXNqovloxji5Q6gOZ0DCIAyLAKHluGSpsXKUGORyBm8Hq0beZIqQ==", - "peerDependencies": { - "final-form": ">=1.2.0" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "node_modules/follow-redirects": { - "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", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", - "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=10", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "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", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "node_modules/harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "dependencies": { - "void-elements": "3.1.0" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-middleware": { - "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", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/i18next": { - "version": "22.0.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.0.4.tgz", - "integrity": "sha512-TOp7BTMKDbUkOHMzDlVsCYWpyaFkKakrrO3HNXfSz4EeJaWwnBScRmgQSTaWHScXVHBUFXTvShrCW8uryBYFcg==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "dependencies": { - "@babel/runtime": "^7.17.2" - } - }, - "node_modules/i18next-browser-languagedetector": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.0.tgz", - "integrity": "sha512-RrH7z5/DbhzhgCLDFIKXBTZlb2aXi38ZHa5e5oZaPt9zGLWmgAX49mzkQL/E7R6Y9fTE8QbZFuyMV0ronu4H/Q==", - "dependencies": { - "@babel/runtime": "^7.19.4" - } - }, - "node_modules/i18next-icu": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.0.3.tgz", - "integrity": "sha512-sZ0VCWDnHysUYQL8j/0rVOxv6rLR+SBoaqQQ2UVNfLyJCuf/bAjYPkoUQgyuDkWFo1xZjeCf4G6GBNr7gD61bQ==", - "peerDependencies": { - "intl-messageformat": "^9.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/idb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.0.tgz", - "integrity": "sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==" - }, - "node_modules/identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "dependencies": { - "harmony-reflect": "^1.4.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/intl-messageformat": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.1.tgz", - "integrity": "sha512-1lrJG2qKzcC1TVzYu1VuB1yiY68LU5rwpbHa2THCzA67Vutkz7+1lv5U20K3Lz5RAiH78zxNztMEtchokMWv8A==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/fast-memoize": "1.2.6", - "@formatjs/icu-messageformat-parser": "2.1.10", - "tslib": "2.4.0" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "dependencies": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "dependencies": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-changed-files/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-changed-files/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-circus/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-jasmine2/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", - "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve-dependencies/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", - "dev": true, - "dependencies": { - "@jest/types": "^29.2.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", - "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", - "dependencies": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^28.0.0", - "jest-watcher": "^28.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "jest": "^27.0.0 || ^28.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watch-typeahead/node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "dependencies": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" - }, - "node_modules/long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", - "dev": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.9.tgz", - "integrity": "sha512-3rm8kbrzpUGRyPKSGuk387NZOwQ90O4rI9tsWQkzNW7BLSnKGp23RsEsKK8N8QVCrtJoAMqy3spxHC4os4G6PQ==", - "dependencies": { - "fs-monkey": "^1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", - "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", - "dependencies": { - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-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/mini-css-extract-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/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "dependencies": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "node_modules/picocolors": { - "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", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-browser-comments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "browserslist": ">=4", - "postcss": ">=8" - } - }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-custom-properties": { - "version": "12.1.10", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", - "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-flexbugs-fixes": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", - "peerDependencies": { - "postcss": "^8.1.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", - "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-normalize": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", - "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", - "dependencies": { - "@csstools/normalize.css": "*", - "postcss-browser-comments": "^4", - "sanitize.css": "*" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "browserslist": ">= 4", - "postcss": ">= 8" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dependencies": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": "^12 || ^14 || >=16" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dependencies": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "dependencies": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", - "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/postcss-svgo/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/postcss-svgo/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/postcss-svgo/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/protobufjs": { - "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", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-app-polyfill": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", - "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", - "dependencies": { - "core-js": "^3.19.2", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.9", - "whatwg-fetch": "^3.6.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-dev-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/react-dev-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/react-dev-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, - "node_modules/react-final-form": { - "version": "6.5.9", - "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", - "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", - "dependencies": { - "@babel/runtime": "^7.15.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/final-form" - }, - "peerDependencies": { - "final-form": "^4.20.4", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-final-form-listeners": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-final-form-listeners/-/react-final-form-listeners-1.0.3.tgz", - "integrity": "sha512-OrdCNxSS4JQS/EXD+R530kZKFqaPfa+WcXPgVro/h4BpaBDF/Ja+BtHyCzDezCIb5rWaGGdOJIj+tN2YdtvrXg==", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "peerDependencies": { - "final-form": ">=4.0.0", - "prop-types": "^15.6.0", - "react": "^15.3.0 || ^16.0.0 || ^17.0.0", - "react-final-form": ">=3.0.0" - } - }, - "node_modules/react-i18next": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.0.0.tgz", - "integrity": "sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==", - "dependencies": { - "@babel/runtime": "^7.14.5", - "html-parse-stringify": "^3.0.1" - }, - "peerDependencies": { - "i18next": ">= 19.0.0", - "react": ">= 16.8.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "node_modules/react-redux": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", - "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-refresh": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", - "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", - "dependencies": { - "@remix-run/router": "1.0.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", - "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", - "dependencies": { - "@remix-run/router": "1.0.2", - "react-router": "6.4.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-scripts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", - "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", - "dependencies": { - "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", - "@svgr/webpack": "^5.5.0", - "babel-jest": "^27.4.2", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.18.1", - "camelcase": "^6.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.1", - "eslint-webpack-plugin": "^3.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "html-webpack-plugin": "^5.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", - "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.0.1", - "prompts": "^2.4.2", - "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.1", - "react-refresh": "^0.11.0", - "resolve": "^1.20.0", - "resolve-url-loader": "^4.0.0", - "sass-loader": "^12.3.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.1", - "tailwindcss": "^3.0.2", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", - "webpack-dev-server": "^4.6.0", - "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" - }, - "bin": { - "react-scripts": "bin/react-scripts.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - }, - "peerDependencies": { - "react": ">= 16", - "typescript": "^3.2.1 || ^4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/react-scripts/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.7.tgz", - "integrity": "sha512-Mxi6lwOmjwIjC1X4gABXMJcKHsOo0xWl3E3ugOgufB8GJU+MqrtY35aBuvCYv/razQ1Vbp7h1gWJjGjoNN5pmA==", - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc" - } - }, - "node_modules/react-window": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz", - "integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-form": { - "version": "8.3.8", - "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz", - "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==", - "dependencies": { - "@babel/runtime": "^7.9.2", - "es6-error": "^4.1.1", - "hoist-non-react-statics": "^3.3.2", - "invariant": "^2.2.4", - "is-promise": "^2.1.0", - "lodash": "^4.17.15", - "prop-types": "^15.6.1", - "react-is": "^16.4.2" - }, - "engines": { - "node": ">=8.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/redux-form" - }, - "peerDependencies": { - "immutable": "^3.8.2 || ^4.0.0", - "react": "^16.4.2 || ^17.0.0", - "react-redux": "^6.0.1 || ^7.0.0", - "redux": "^3.7.2 || ^4.0.0" - }, - "peerDependenciesMeta": { - "immutable": { - "optional": true - } - } - }, - "node_modules/redux-form/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", - "peerDependencies": { - "redux": "^4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=8.9" - }, - "peerDependencies": { - "rework": "1.0.1", - "rework-visit": "1.0.0" - }, - "peerDependenciesMeta": { - "rework": { - "optional": true - }, - "rework-visit": { - "optional": true - } - } - }, - "node_modules/resolve-url-loader/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/resolve-url-loader/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "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" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-html": { - "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": "^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", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" - }, - "node_modules/sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - } - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "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" - } - }, - "node_modules/source-map-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "dependencies": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-loader": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/tailwindcss": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz", - "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==", - "dependencies": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.17", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/tailwindcss/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "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.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "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", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@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.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "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", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/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-dev-middleware/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-dev-middleware/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/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-dev-server/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-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "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" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", - "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "webpack": "^4.44.2 || ^5.47.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "engines": { - "node": ">=10.13.0" - } - }, - "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.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "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", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/workbox-build/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/workbox-build/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-build/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/workbox-build/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "node_modules/workbox-build/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" - }, - "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" - }, - "node_modules/workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", - "dependencies": { - "fast-json-stable-stringify": "^2.1.0", - "pretty-bytes": "^5.4.1", - "upath": "^1.2.0", - "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.4.0 || ^5.9.0" - } - }, - "node_modules/workbox-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "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" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "requires": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - } - }, - "@babel/code-frame": { - "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/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.0.tgz", - "integrity": "sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w==" - }, - "@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" - } - } - }, - "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "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==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", - "requires": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", - "requires": { - "@babel/types": "^7.19.4" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "requires": { - "@babel/types": "^7.20.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "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.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", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" - }, - "@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helpers": { - "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.27.1", - "@babel/types": "^7.27.1" - } - }, - "@babel/parser": { - "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", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-decorators": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.0.tgz", - "integrity": "sha512-vnuRRS20ygSxclEYikHzVrP9nZDFXaSzvJxGLQNAiBX041TmhS4hOUHWNIpq/q4muENuEP9XPJFXTNFejhemkg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.19.1", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.19.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-decorators": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", - "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", - "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", - "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.18.12.tgz", - "integrity": "sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz", - "integrity": "sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" - }, - "@babel/runtime-corejs3": { - "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.30.2" - } - }, - "@babel/template": { - "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.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - } - }, - "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "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.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" - }, - "@csstools/normalize.css": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", - "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" - }, - "@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", - "requires": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" - } - }, - "@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==" - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==" - }, - "@emotion/babel-plugin": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", - "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.17.12", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.1.3" - } - }, - "@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", - "requires": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" - } - }, - "@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" - }, - "@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "requires": { - "@emotion/memoize": "^0.8.0" - } - }, - "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "@emotion/react": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", - "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - } - }, - "@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", - "requires": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", - "csstype": "^3.0.2" - } - }, - "@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" - }, - "@emotion/styled": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", - "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" - } - }, - "@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==" - }, - "@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" - }, - "@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" - }, - "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } - } - }, - "@formatjs/ecma402-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.13.0.tgz", - "integrity": "sha512-CQ8Ykd51jYD1n05dtoX6ns6B9n/+6ZAxnWUAonvHC4kkuAemROYBhHkEB4tm1uVrRlE7gLDqXkAnY51Y0pRCWQ==", - "requires": { - "@formatjs/intl-localematcher": "0.2.31", - "tslib": "2.4.0" - } - }, - "@formatjs/fast-memoize": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.6.tgz", - "integrity": "sha512-9CWZ3+wCkClKHX+i5j+NyoBVqGf0pIskTo6Xl6ihGokYM2yqSSS68JIgeo+99UIHc+7vi9L3/SDSz/dWI9SNlA==", - "requires": { - "tslib": "2.4.0" - } - }, - "@formatjs/icu-messageformat-parser": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.10.tgz", - "integrity": "sha512-KkRMxhifWkRC45dhM9tqm0GXbb6NPYTGVYY3xx891IKc6p++DQrZTnmkVSNNO47OEERLfuP2KkPFPJBuu8z/wg==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/icu-skeleton-parser": "1.3.14", - "tslib": "2.4.0" - } - }, - "@formatjs/icu-skeleton-parser": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.14.tgz", - "integrity": "sha512-7bv60HQQcBb3+TSj+45tOb/CHV5z1hOpwdtS50jsSBXfB+YpGhnoRsZxSRksXeCxMy6xn6tA6VY2601BrrK+OA==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "tslib": "2.4.0" - } - }, - "@formatjs/intl-localematcher": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.31.tgz", - "integrity": "sha512-9QTjdSBpQ7wHShZgsNzNig5qT3rCPvmZogS/wXZzKotns5skbXgs0I7J8cuN0PPqXyynvNVuN+iOKhNS2eb+ZA==", - "requires": { - "tslib": "2.4.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" - }, - "@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.2.tgz", - "integrity": "sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "requires": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - } - }, - "@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/set-array": { - "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.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.5", - "@jridgewell/trace-mapping": "^0.3.25" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "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.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "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" - } - }, - "@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" - }, - "@mui/base": { - "version": "5.0.0-alpha.103", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", - "integrity": "sha512-fJIyB2df3CHn7D26WHnutnY7vew6aytTlhmRJz6GX7ag19zU2GcOUhJAzY5qwWcrXKnlYgzimhEjaEnuiUWU4g==", - "requires": { - "@babel/runtime": "^7.19.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@mui/core-downloads-tracker": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.11.tgz", - "integrity": "sha512-u5ff+UCFDHcR8MoQ8tuJR4c35vt7T/ki3aMEE2O3XQoGs8KJSrBiisFpFKyldg9/W2NSyoZxN+kxEGIfRxh+9Q==" - }, - "@mui/icons-material": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.10.9.tgz", - "integrity": "sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==", - "requires": { - "@babel/runtime": "^7.19.0" - } - }, - "@mui/material": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.11.tgz", - "integrity": "sha512-KJ0wPCTbv6sFzwA3dgg0gowdfF+SRl7D510J9l6Nl/KFX0EawcewQudqKY4slYGFXniKa5PykqokpaWXsCCPqg==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/base": "5.0.0-alpha.103", - "@mui/core-downloads-tracker": "^5.10.11", - "@mui/system": "^5.10.10", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - } - }, - "@mui/private-theming": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.10.9.tgz", - "integrity": "sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/utils": "^5.10.9", - "prop-types": "^15.8.1" - } - }, - "@mui/styled-engine": { - "version": "5.10.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz", - "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==", - "requires": { - "@babel/runtime": "^7.19.0", - "@emotion/cache": "^11.10.3", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/system": { - "version": "5.10.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.10.tgz", - "integrity": "sha512-TXwtKN0adKpBrZmO+eilQWoPf2veh050HLYrN78Kps9OhlvO70v/2Kya0+mORFhu9yhpAwjHXO8JII/R4a5ZLA==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/private-theming": "^5.10.9", - "@mui/styled-engine": "^5.10.8", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.0.tgz", - "integrity": "sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==" - }, - "@mui/utils": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.9.tgz", - "integrity": "sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==", - "requires": { - "@babel/runtime": "^7.19.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.8.tgz", - "integrity": "sha512-wxXRwf+IQ6zvHSJZ+5T2RQNEsq+kx4jKRXfFvdt3nBIUzJUAvXEFsUeoaohDe/Kr84MTjGwcuIUPNcstNJORsA==", - "requires": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" - } - } - }, - "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@remix-run/router": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", - "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==" - }, - "@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "requires": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - } - }, - "@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "requires": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - } - } - }, - "@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" - }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "requires": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==" - }, - "@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - } - }, - "@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "requires": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "requires": { - "@babel/types": "^7.12.6" - } - }, - "@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "requires": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - } - }, - "@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "requires": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - } - }, - "@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "requires": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - } - }, - "@testing-library/dom": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", - "integrity": "sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", - "dev": true, - "requires": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" - }, - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "requires": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "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.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" - }, - "@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "requires": { - "@types/node": "*" - } - }, - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", - "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, - "@types/jquery": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", - "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", - "dev": true, - "requires": { - "@types/sizzle": "*" - } - }, - "@types/json-schema": { - "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", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" - }, - "@types/lodash": { - "version": "4.14.186", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", - "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", - "dev": true - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "@types/node": { - "version": "18.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/react": { - "version": "18.0.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", - "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "18.0.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", - "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", - "dev": true, - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-virtualized-auto-sizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz", - "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-window": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz", - "integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/redux-form": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@types/redux-form/-/redux-form-8.3.5.tgz", - "integrity": "sha512-SchB4i7nxgWNbJS4cXEZducztkvHzVrb5xlAXwfLpbrLPo6tMY06+kx1GqMv42+YnGy9TpCAkF51a21HatqWBA==", - "dev": true, - "requires": { - "@types/react": "*", - "redux": "^3.6.0 || ^4.0.0" - } - }, - "@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "requires": { - "@types/node": "*" - } - }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" - }, - "@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "requires": { - "@types/express": "*" - } - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dev": true, - "requires": { - "@types/jest": "*" - } - }, - "@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" - }, - "@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "requires": { - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", - "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.41.0.tgz", - "integrity": "sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==", - "requires": { - "@typescript-eslint/utils": "5.41.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", - "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", - "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", - "requires": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==" - }, - "@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", - "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", - "requires": { - "@typescript-eslint/types": "5.41.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "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.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "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.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.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.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.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "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.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.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.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.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.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.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.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.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "requires": { - "@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.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.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.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.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.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.14.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - } - } - }, - "acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - } - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "address": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", - "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==" - }, - "adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "requires": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - } - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "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==" - } - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==" - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "aria-query": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.2.tgz", - "integrity": "sha512-JWydkr9MirMg2jGJstDqDgzoHqaFbv7n1ghfXYdtEgXWgdq3jz7IU3SQvtj9k3mAszQBiTpQhFdlH+JIRuGTzg==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" - }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, - "autoprefixer": { - "version": "10.4.13", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", - "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001426", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "axe-core": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.0.tgz", - "integrity": "sha512-4+rr8eQ7+XXS5nZrKcMO/AikHL0hVqy+lHWAnE3xdHl+aguag8SOQ6eEqLexwLNWgXIMfunGuD3ON1/6Kyet0A==" - }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" - }, - "babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "requires": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "requires": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - } - }, - "babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "requires": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "babel-preset-react-app": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", - "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", - "requires": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==" - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" - }, - "bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", - "requires": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "bonjour-service": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", - "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", - "requires": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "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.1.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "requires": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==" - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" - }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==" - }, - "case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" - }, - "check-types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", - "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" - }, - "ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" - }, - "clean-css": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", - "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", - "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" - }, - "common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" - }, - "core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", - "requires": { - "browserslist": "^4.21.4" - } - }, - "core-js-pure": { - "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", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" - }, - "css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==" - }, - "css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", - "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "requires": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.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.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "cssdb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.2.tgz", - "integrity": "sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz", - "integrity": "sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==", - "requires": { - "cssnano-preset-default": "^5.2.13", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", - "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", - "requires": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.3", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.1", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - } - }, - "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "requires": { - "css-tree": "^1.1.2" - }, - "dependencies": { - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - } - } - }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" - }, - "deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, - "default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "requires": { - "execa": "^5.0.0" - } - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" - }, - "detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "requires": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - } - }, - "dexie": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", - "integrity": "sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==" - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, - "diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "requires": { - "path-type": "^4.0.0" - } - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, - "dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", - "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", - "dev": true - }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "requires": { - "utila": "~0.4" - } - }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" - } - } - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" - }, - "dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "requires": { - "jake": "^10.8.5" - } - }, - "electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - }, - "enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "requires": { - "stackframe": "^1.3.4" - } - }, - "es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" - }, - "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - }, - "escalade": { - "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", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", - "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", - "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } - } - }, - "eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", - "requires": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-flowtype": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", - "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", - "requires": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", - "requires": { - "@typescript-eslint/experimental-utils": "^5.0.0" - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "requires": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "dependencies": { - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - } - } - }, - "eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", - "requires": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==" - }, - "eslint-plugin-testing-library": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz", - "integrity": "sha512-6BQp3tmb79jLLasPHJmy8DnxREe+2Pgf7L+7o09TSWPfdqqtQfRZmZNetr5mOs3yqZk/MRNxpN3RUpJe0wB4LQ==", - "requires": { - "@typescript-eslint/utils": "^5.13.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" - }, - "eslint-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", - "requires": { - "@types/eslint": "^7.29.0 || ^8.4.1", - "jest-worker": "^28.0.2", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.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" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "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.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==" - }, - "expect": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.2.tgz", - "integrity": "sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.2.2", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.2", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" - } - }, - "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "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", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - } - }, - "filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "requires": { - "minimatch": "^5.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" - }, - "fill-range": { - "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" - } - }, - "final-form": { - "version": "4.20.7", - "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.7.tgz", - "integrity": "sha512-ii3X9wNfyBYFnDPunYN5jh1/HAvtOZ9aJI/TVk0MB86hZuOeYkb+W5L3icgwW9WWNztZR6MDU3En6eoZTUoFPg==", - "requires": { - "@babel/runtime": "^7.10.0" - } - }, - "final-form-set-field-touched": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/final-form-set-field-touched/-/final-form-set-field-touched-1.0.1.tgz", - "integrity": "sha512-yvE5AAs9U3OgJQ9YF8NhSF0I0mJEECvOpkaXNqovloxji5Q6gOZ0DCIAyLAKHluGSpsXKUGORyBm8Hq0beZIqQ==" - }, - "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "follow-redirects": { - "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", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "fork-ts-checker-webpack-plugin": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", - "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - } - } - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "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", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "requires": { - "duplexer": "^0.1.2" - } - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { - "function-bind": "^1.1.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "requires": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - } - }, - "html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "requires": { - "void-elements": "3.1.0" - } - }, - "html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", - "requires": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-proxy-middleware": { - "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", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, - "husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", - "dev": true - }, - "i18next": { - "version": "22.0.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.0.4.tgz", - "integrity": "sha512-TOp7BTMKDbUkOHMzDlVsCYWpyaFkKakrrO3HNXfSz4EeJaWwnBScRmgQSTaWHScXVHBUFXTvShrCW8uryBYFcg==", - "requires": { - "@babel/runtime": "^7.17.2" - } - }, - "i18next-browser-languagedetector": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.0.tgz", - "integrity": "sha512-RrH7z5/DbhzhgCLDFIKXBTZlb2aXi38ZHa5e5oZaPt9zGLWmgAX49mzkQL/E7R6Y9fTE8QbZFuyMV0ronu4H/Q==", - "requires": { - "@babel/runtime": "^7.19.4" - } - }, - "i18next-icu": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.0.3.tgz", - "integrity": "sha512-sZ0VCWDnHysUYQL8j/0rVOxv6rLR+SBoaqQQ2UVNfLyJCuf/bAjYPkoUQgyuDkWFo1xZjeCf4G6GBNr7gD61bQ==" - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" - }, - "idb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.0.tgz", - "integrity": "sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==" - }, - "identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "requires": { - "harmony-reflect": "^1.4.6" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" - }, - "immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "intl-messageformat": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.1.tgz", - "integrity": "sha512-1lrJG2qKzcC1TVzYu1VuB1yiY68LU5rwpbHa2THCzA67Vutkz7+1lv5U20K3Lz5RAiH78zxNztMEtchokMWv8A==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/fast-memoize": "1.2.6", - "@formatjs/icu-messageformat-parser": "2.1.10", - "tslib": "2.4.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==" - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" - }, - "is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==" - }, - "is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==" - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "requires": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "requires": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - } - }, - "jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "requires": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "requires": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "requires": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "requires": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "requires": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - } - } - }, - "jest-matcher-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", - "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" - }, - "jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "requires": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "requires": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", - "dev": true, - "requires": { - "@jest/types": "^29.2.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "requires": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watch-typeahead": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", - "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", - "requires": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^28.0.0", - "jest-watcher": "^28.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - } - } - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==" - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } - } - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" - }, - "string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "requires": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==" - } - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "requires": { - "ansi-regex": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "requires": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==" - }, - "jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "requires": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==" - }, - "language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" - }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==" - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==" - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" - }, - "long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "requires": { - "tslib": "^2.0.3" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", - "dev": true - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "requires": { - "tmpl": "1.0.5" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memfs": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.9.tgz", - "integrity": "sha512-3rm8kbrzpUGRyPKSGuk387NZOwQ90O4rI9tsWQkzNW7BLSnKGp23RsEsKK8N8QVCrtJoAMqy3spxHC4os4G6PQ==", - "requires": { - "fs-monkey": "^1.0.3" - } - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "mini-css-extract-plugin": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", - "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", - "requires": { - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.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.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "requires": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - } - }, - "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, - "node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" - }, - "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "requires": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - } - }, - "object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "requires": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "picocolors": { - "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", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" - } - } - }, - "postcss": { - "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.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-browser-comments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" - }, - "postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "requires": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-properties": { - "version": "12.1.10", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", - "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==" - }, - "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" - }, - "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" - }, - "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" - }, - "postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-flexbugs-fixes": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" - }, - "postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" - }, - "postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==" - }, - "postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" - }, - "postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "requires": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" - }, - "postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" - }, - "postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - } - }, - "postcss-merge-rules": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", - "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "requires": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-normalize": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", - "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", - "requires": { - "@csstools/normalize.css": "*", - "postcss-browser-comments": "^4", - "sanitize.css": "*" - } - }, - "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" - }, - "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "requires": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==" - }, - "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "requires": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" - }, - "postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "requires": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-reduce-initial": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", - "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" - }, - "postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - } - } - } - }, - "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" - }, - "pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "requires": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "requires": { - "asap": "~2.0.6" - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "protobufjs": { - "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", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - }, - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-app-polyfill": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", - "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", - "requires": { - "core-js": "^3.19.2", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.9", - "whatwg-fetch": "^3.6.2" - } - }, - "react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "requires": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, - "react-final-form": { - "version": "6.5.9", - "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", - "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", - "requires": { - "@babel/runtime": "^7.15.4" - } - }, - "react-final-form-listeners": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-final-form-listeners/-/react-final-form-listeners-1.0.3.tgz", - "integrity": "sha512-OrdCNxSS4JQS/EXD+R530kZKFqaPfa+WcXPgVro/h4BpaBDF/Ja+BtHyCzDezCIb5rWaGGdOJIj+tN2YdtvrXg==", - "requires": { - "@babel/runtime": "^7.12.5" - } - }, - "react-i18next": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.0.0.tgz", - "integrity": "sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==", - "requires": { - "@babel/runtime": "^7.14.5", - "html-parse-stringify": "^3.0.1" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "react-redux": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", - "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - } - }, - "react-refresh": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" - }, - "react-router": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", - "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", - "requires": { - "@remix-run/router": "1.0.2" - } - }, - "react-router-dom": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", - "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", - "requires": { - "@remix-run/router": "1.0.2", - "react-router": "6.4.2" - } - }, - "react-scripts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", - "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", - "requires": { - "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", - "@svgr/webpack": "^5.5.0", - "babel-jest": "^27.4.2", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.18.1", - "camelcase": "^6.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.1", - "eslint-webpack-plugin": "^3.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "fsevents": "^2.3.2", - "html-webpack-plugin": "^5.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", - "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.0.1", - "prompts": "^2.4.2", - "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.1", - "react-refresh": "^0.11.0", - "resolve": "^1.20.0", - "resolve-url-loader": "^4.0.0", - "sass-loader": "^12.3.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.1", - "tailwindcss": "^3.0.2", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", - "webpack-dev-server": "^4.6.0", - "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "react-virtualized-auto-sizer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.7.tgz", - "integrity": "sha512-Mxi6lwOmjwIjC1X4gABXMJcKHsOo0xWl3E3ugOgufB8GJU+MqrtY35aBuvCYv/razQ1Vbp7h1gWJjGjoNN5pmA==" - }, - "react-window": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz", - "integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==", - "requires": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - } - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "requires": { - "pify": "^2.3.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "requires": { - "minimatch": "^3.0.5" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-form": { - "version": "8.3.8", - "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz", - "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==", - "requires": { - "@babel/runtime": "^7.9.2", - "es6-error": "^4.1.1", - "hoist-non-react-statics": "^3.3.2", - "invariant": "^2.2.4", - "is-promise": "^2.1.0", - "lodash": "^4.17.15", - "prop-types": "^15.6.1", - "react-is": "^16.4.2" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" - }, - "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" - }, - "renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", - "requires": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==" - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "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" - } - }, - "rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-html": { - "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": "^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": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" - }, - "sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "requires": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" - }, - "selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "requires": { - "node-forge": "^1" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, - "send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - } - } - }, - "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - } - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==" - }, - "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "requires": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - }, - "source-map-js": { - "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", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "requires": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - } - } - }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" - }, - "strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "style-loader": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" - }, - "stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "requires": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - } - }, - "stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - }, - "dependencies": { - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - } - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - } - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "tailwindcss": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz", - "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==", - "requires": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.17", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "dependencies": { - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "requires": { - "is-glob": "^4.0.3" - } - } - } - }, - "tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" - }, - "temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==" - }, - "tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "requires": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "dependencies": { - "type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==" - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "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.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "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": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - } - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "requires": { - "punycode": "^2.1.1" - } - }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "requires": { - "minimist": "^1.2.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" - } - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" - }, - "update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" - } - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" - }, - "webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", - "requires": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@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.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "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.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "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.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", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.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.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } - } - }, - "webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", - "requires": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.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.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==" - } - } - }, - "webpack-manifest-plugin": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", - "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", - "requires": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - } - } - } - }, - "webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==" - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" - } - }, - "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==" - }, - "workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "requires": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "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==" - }, - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "requires": { - "whatwg-url": "^7.0.0" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" - }, - "workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "requires": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "requires": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" - }, - "workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", - "requires": { - "fast-json-stable-stringify": "^2.1.0", - "pretty-bytes": "^5.4.1", - "upath": "^1.2.0", - "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } - } - }, - "workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "requires": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "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", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - } - } -} diff --git a/webclient/package.json b/webclient/package.json deleted file mode 100644 index a578fc9d9..000000000 --- a/webclient/package.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "name": "webclient", - "version": "1.0.0", - "private": true, - "scripts": { - "prebuild": "node prebuild.js", - "prestart": "node prebuild.js", - "build": "react-scripts build", - "start": "react-scripts start", - "test": "react-scripts test", - "test:watch": "react-scripts test", - "eject": "react-scripts eject", - "lint": "eslint \"./**/*.{ts,tsx}\"", - "lint:fix": "eslint \"./**/*.{ts,tsx}\" --fix", - "golden": "npm run lint && npm run test", - "prepare": "cd .. && husky install", - "translate": "node prebuild.js -i18nOnly" - }, - "dependencies": { - "@emotion/react": "^11.8.2", - "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^5.5.1", - "@mui/material": "^5.5.1", - "crypto-js": "^4.2.0", - "dexie": "^3.2.2", - "final-form": "^4.20.6", - "final-form-set-field-touched": "^1.0.1", - "i18next": "^22.0.4", - "i18next-browser-languagedetector": "^7.0.0", - "i18next-icu": "^2.0.3", - "intl-messageformat": "^10.2.1", - "lodash": "^4.17.21", - "prop-types": "^15.8.1", - "protobufjs": "^7.2.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-final-form": "^6.5.8", - "react-final-form-listeners": "^1.0.3", - "react-i18next": "^12.0.0", - "react-redux": "^8.0.4", - "react-router-dom": "^6.2.2", - "react-scripts": "5.0.1", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6", - "redux": "^4.1.2", - "redux-form": "^8.3.8", - "redux-thunk": "^2.4.1", - "rxjs": "^7.5.4", - "sanitize-html": "^2.7.3" - }, - "devDependencies": { - "@babel/core": "^7.17.5", - "@mui/types": "^7.1.3", - "@testing-library/jest-dom": "^5.16.2", - "@testing-library/react": "^13.4.0", - "@types/jest": "29.2.0", - "@types/jquery": "^3.5.14", - "@types/lodash": "^4.14.179", - "@types/node": "18.11.7", - "@types/prop-types": "^15.7.4", - "@types/react": "18.0.24", - "@types/react-dom": "18.0.8", - "@types/react-redux": "^7.1.23", - "@types/react-router-dom": "^5.3.3", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@types/redux-form": "^8.3.3", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", - "fs-extra": "^10.0.1", - "husky": "^8.0.1", - "typescript": "^4.6.2" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "jest": { - "moduleNameMapper": { - "\\.(css|less)$": "identity-obj-proxy" - } - } -} diff --git a/webclient/prebuild.js b/webclient/prebuild.js deleted file mode 100644 index 3b420f32c..000000000 --- a/webclient/prebuild.js +++ /dev/null @@ -1,112 +0,0 @@ -const fse = require('fs-extra'); -const path = require('path'); -const util = require('util'); -const exec = util.promisify(require('child_process').exec); - -const ROOT_DIR = './src'; -const PUBLIC_DIR = './public'; - -const protoFilesDir = `${PUBLIC_DIR}/pb`; -const i18nDefaultFile = `${ROOT_DIR}/i18n-default.json`; -const serverPropsFile = `${ROOT_DIR}/server-props.json`; -const masterProtoFile = `${ROOT_DIR}/proto-files.json`; - -const sharedFiles = [ - ['../libcockatrice_protocol/libcockatrice/protocol/pb', protoFilesDir], - ['../cockatrice/resources/countries', `${ROOT_DIR}/images/countries`], -]; - -const i18nFileRegex = /\.i18n\.json$/; - -const i18nOnly = process.argv.indexOf('-i18nOnly') > -1; - -(async () => { - if (i18nOnly) { - await createI18NDefault(); - return; - } - - // make sure these files finish copying before master file is created - await copySharedFiles(); - - await createMasterProtoFile(); - await createServerProps(); - await createI18NDefault(); -})(); - -async function copySharedFiles() { - try { - return await Promise.all(sharedFiles.map(([src, dest]) => fse.copy(src, dest, { overwrite: true }))); - } catch (e) { - console.error(e); - process.exitCode = 1; - } -} - -async function createMasterProtoFile() { - try { - fse.readdir(protoFilesDir, (err, files) => { - if (err) throw err; - - fse.outputFile(masterProtoFile, JSON.stringify(files.filter(file => /\.proto$/.test(file)))); - }); - } catch (e) { - console.error(e); - process.exitCode = 1; - } -} - -async function createServerProps() { - try { - fse.outputFile(serverPropsFile, JSON.stringify({ - REACT_APP_VERSION: await getCommitHash(), - })); - } catch (e) { - console.error(e); - process.exitCode = 1; - } -} - -async function createI18NDefault() { - try { - const files = getAllFiles(ROOT_DIR, i18nFileRegex); - const allJson = await Promise.all(files.map(file => fse.readJson(file))); - - const rollup = allJson.reduce((acc, json) => { - const newKeys = Object.keys(json); - - newKeys.forEach(key => { - if (acc[key]) { - throw new Error(`i18n key collision: ${key}\n${JSON.stringify(json)}`); - } - - acc[key] = json[key]; - }); - - return acc; - }, {}); - - fse.outputFile(i18nDefaultFile, JSON.stringify(rollup, null, 2)); - } catch (e) { - console.error(e); - process.exitCode = 1; - } -} - -async function getCommitHash() { - return (await exec('git rev-parse HEAD')).stdout.trim(); -} - -function getAllFiles(dirPath, regex = /./, allFiles = []) { - return fse.readdirSync(dirPath).reduce((files, file) => { - const filePath = dirPath + "/" + file; - - if (fse.statSync(filePath).isDirectory()) { - files.concat(getAllFiles(filePath, regex, files)); - } else if (regex.test(file)) { - files.push(path.join(__dirname, filePath)); - } - - return files; - }, allFiles); -} diff --git a/webclient/public/favicon.ico b/webclient/public/favicon.ico deleted file mode 100644 index c2c86b859..000000000 Binary files a/webclient/public/favicon.ico and /dev/null differ diff --git a/webclient/public/index.html b/webclient/public/index.html deleted file mode 100644 index c050e82f4..000000000 --- a/webclient/public/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - Webatrice - - - -
- - - diff --git a/webclient/public/locales/de/translation.json b/webclient/public/locales/de/translation.json deleted file mode 100644 index 294f3fb9f..000000000 --- a/webclient/public/locales/de/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Deutsch (German)", - "disconnect": "Verbindung trennen", - "label": { - "confirmPassword": "Passwort bestätigen", - "confirmSure": "Sind Sie sicher?", - "country": "Land", - "delete": "Löschen", - "email": "E-Mail", - "hostName": "Name des Hosts", - "hostAddress": "Adresse des Hosts", - "password": "Passwort", - "passwordAgain": "Passwort Erneut", - "port": "Port", - "realName": "Richtiger Name", - "saveChanges": "Änderungen speichern", - "token": "Token", - "username": "Benutzername" - }, - "validation": { - "minChars": "Mindestens {count} {count, plural, one {Zeichen} other {Zeichen}} benötigt", - "passwordsMustMatch": "Die Passwörter stimmen nicht überein", - "required": "Benötigt" - }, - "countries": { - "AD": "Andorra", - "AE": "Vereinigte Arabische Emirate", - "AF": "Afghanistan", - "AG": "Antigua und Barbuda", - "AI": "Anguilla", - "AL": "Albanien", - "AM": "Armenien", - "AO": "Angola", - "AQ": "Antarktis", - "AR": "Argentinien", - "AS": "Amerikanisch-Samoa", - "AT": "Österreich", - "AU": "Australien", - "AW": "Aruba", - "AX": "Ålandinseln", - "AZ": "Aserbaidschan", - "BA": "Bosnien und Herzegowina", - "BB": "Barbados", - "BD": "Bangladesch", - "BE": "Belgien", - "BF": "Burkina Faso", - "BG": "Bulgarien", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint-Barthélemy ", - "BM": "Bermuda", - "BN": "Brunei", - "BO": "Bolivien", - "BQ": "Bonaire, Sint Eustatius und Saba", - "BR": "Brasilien", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvetinsel", - "BW": "Botswana", - "BY": "Weißrussland", - "BZ": "Belize", - "CA": "Kanada", - "CC": "Kokosinseln", - "CD": "DR Kongo", - "CF": "Zentralafrikanische Republik", - "CG": "Republik Kongo", - "CH": "Schweiz", - "CI": "Elfenbeinküste", - "CK": "Cookinseln", - "CL": "Chile", - "CM": "Kamerun", - "CN": "China", - "CO": "Kolumbien", - "CR": "Costa Rica", - "CU": "Kuba", - "CV": "Kap Verde", - "CW": "Curaçao", - "CX": "Weihnachtsinsel", - "CY": "Zypern", - "CZ": "Tschechien", - "DE": "Deutschland", - "DJ": "Dschibuti", - "DK": "Dänemark", - "DM": "Dominica", - "DO": "Dominikanische Republik", - "DZ": "Algerien", - "EC": "Ecuador", - "EE": "Estland", - "EG": "Ägypten", - "EH": "Westsahara", - "ER": "Eritrea", - "ES": "Spanien", - "ET": "Äthiopien", - "FI": "Finnland", - "FJ": "Fiji", - "FK": "Falklandinseln", - "FM": "Mikronesien", - "FO": "Färöer", - "FR": "Frankreich", - "GA": "Gabun", - "GB": "Vereinigtes Königreich", - "GD": "Grenada", - "GE": "Georgien", - "GF": "Französisch-Guayana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Grönland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guandeloupe", - "GQ": "Äquatorialguinea", - "GR": "Griechenland", - "GS": "Südgeorgien und die Südlichen Sandwichinseln", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hongkong", - "HM": "Heard und McDonaldinseln", - "HN": "Honduras", - "HR": "Kroatien", - "HT": "Haiti", - "HU": "Ungarn", - "ID": "Indonesien", - "IE": "Irland", - "IL": "Israel", - "IM": "Insel Man", - "IN": "Indien", - "IO": " Britisches Territorium im Indischen Ozean", - "IQ": "Irak", - "IR": "Iran", - "IS": "Island", - "IT": "Italien", - "JE": "Jersey", - "JM": "Jamaika", - "JO": "Jordanien", - "JP": "Japan", - "KE": "Kenia", - "KG": "Kirgisistan", - "KH": "Kambodscha", - "KI": "Kiribati", - "KM": "Komoren", - "KN": "St. Kitts und Nevis", - "KP": "Nordkorea", - "KR": "Südkorea", - "KW": "Kuwait", - "KY": "Kaimaninseln", - "KZ": "Kasachstan", - "LA": "Laos", - "LB": "Libanon", - "LC": "St. Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Litauen", - "LU": "Luxemburg", - "LV": "Lettland", - "LY": "Libyen", - "MA": "Marokko", - "MC": "Monaco", - "MD": "Moldau", - "ME": "Montenegro", - "MF": "Saint-Martin (Französischer Teil)", - "MG": "Madagaskar", - "MH": "Marshallinseln", - "MK": "Nordmazedonien", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolei", - "MO": "Macau", - "MP": "Nördliche Marianen", - "MQ": "Martinique", - "MR": "Mauretanien", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Malediven", - "MW": "Malawi", - "MX": "Mexiko", - "MY": "Malaysia", - "MZ": "Mosambik", - "NA": "Namibia", - "NC": "Neukaledonien", - "NE": "Niger", - "NF": "Norfolkinsel", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Niederlande", - "NO": "Norwegen", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "Neuseeland", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "Französisch-Polynesien", - "PG": "Papua-Neuguinea", - "PH": "Philippinen", - "PK": "Pakistan", - "PL": "Polen", - "PM": "St. Pierre und Miquelon", - "PN": "Pitcairninseln", - "PR": "Puerto Rico", - "PS": "Palästina", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Katar", - "RE": "Réunion", - "RO": "Rumänien", - "RS": "Serbien", - "RU": "Russland", - "RW": "Ruanda", - "SA": "Saudi-Arabien", - "SB": "Salomonen", - "SC": "Seychellen", - "SD": "Sudan", - "SE": "Schweden", - "SG": "Singapur", - "SH": "St. Helena, Ascension und Tristan da Cunha", - "SI": "Slowenien", - "SJ": "Spitzbergen und Jan Mayen", - "SK": "Slowakei", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "Südsudan", - "ST": "São Tomé und Príncipe", - "SV": "El Salvador", - "SX": "Sint Maarten (Niederländischer Teil)", - "SY": "Syrien", - "SZ": "Eswatini", - "TC": "Turks- und Caicosinseln", - "TD": "Tschad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tadschikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunesien", - "TO": "Tonga", - "TR": "Türkei", - "TT": "Trinidad und Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tansania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "Kleinere abgelegene Inseln der Vereinigten Staaten", - "US": "Vereinigte Staaten von Amerika", - "UY": "Uruguay", - "UZ": "Usbekistan", - "VA": "Heiliger Stuhl", - "VC": "St. Vincent und die Grenadinen", - "VE": "Venezuela", - "VG": "Britische Jungferninseln", - "VI": "Amerikanische Jungferninseln", - "VN": "Vietnam", - "VU": "Vanuatu", - "WF": "Wallis und Futuna", - "WS": "Samoa", - "YE": "Jemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "Südafrika", - "ZM": "Sambia", - "ZW": "Simbabwe", - "EU": "Europäische Union" - }, - "languages": { - "en-US": "Englisch - US", - "fr": "Französisch", - "nl": "Niederländisch", - "pt_BR": "Portugiesisch - Brasilien" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Neuen Host hinzufügen", - "toast": "Host erfolgreich {mode, select, created {erstellt} deleted {gelöscht} other {bearbeitet}}." - }, - "InitializeContainer": { - "title": "WUSSTEN SIE SCHON", - "subtitle": "<1>Cockatrice wird von Freiwilligen geleitet <1>die Kartenspiele lieben!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "Ein plattformübergreifender, virtueller Tabletop für Multiplayer-Kartenspiele." - }, - "footer": { - "registerPrompt": "Noch nicht registriert?", - "registerAction": "Einen Account erstellen", - "credit": "Cockatrice ist ein Open-Source-Projekt", - "version": "Version" - }, - "content": { - "subtitle1": "Spielen Sie Kartenspiele im Multiplayer online.", - "subtitle2": "Plattformübergreifender, virtueller Tabletop für Kartenspiele im Multiplayer. Für immer kostenlos." - }, - "toasts": { - "passwordResetSuccessToast": "Passwort erfolgreich zurückgesetzt", - "accountActivationSuccess": "Account erfolgreich aktiviert" - } - }, - "UnsupportedContainer": { - "title": "Browser wird nicht unterstützt", - "subtitle1": "Bitte updaten Sie Ihren Browser und/oder überprüfen Sie Ihre Berechtigungen.", - "subtitle2": "Hinweis: Beim privaten Surfen werden bei einigen Browsern bestimmte Berechtigungen oder Funktionen deaktiviert." - }, - "AccountActivationDialog": { - "title": "Aktivierung des Accounts", - "subtitle1": "Ihr Account wurde noch nicht aktiviert.", - "subtitle2": "Sie müssen das Aktivierungs-Token angeben, das Sie in der Aktivierungs-E-Mail erhalten haben." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Bearbeiten} other {Hinzufügen}} Bekannter Host", - "subtitle": "Wenn Sie einen neuen Host hinzufügen, können Sie sich mit verschiedenen Servern verbinden. Geben Sie die unten stehenden Details in Ihre Hostliste ein." - }, - "RegistrationDialog": { - "title": "Einen neuen Account erstellen" - }, - "RequestPasswordResetDialog": { - "title": "Passwort-Zurücksetzung anfordern" - }, - "ResetPasswordDialog": { - "title": "Passwort zurücksetzen" - }, - "AccountActivationForm": { - "error": { - "failed": "Account-Aktivierung fehlgeschlagen" - }, - "label": { - "activate": "Account aktivieren" - } - }, - "KnownHostForm": { - "help": "Brauchen Sie Hilfe beim Hinzufügen eines neuen Hosts?", - "label": { - "add": "Host hinzufügen", - "find": "Host finden" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Automatisch verbinden", - "forgot": "Passwort vergessen", - "login": "Login", - "savePassword": "Passwort speichern", - "savedPassword": "Passwort gespeichert" - } - }, - "RegisterForm": { - "label": { - "register": "Registrieren" - }, - "toast": { - "registerSuccess": "Registrierung erfolgreich!" - } - }, - "RequestPasswordResetForm": { - "error": "Anfrage zum Zurücksetzen des Passworts fehlgeschlagen", - "mfaEnabled": "Der Server hat Multi-Faktor-Authentifizierung aktiviert", - "request": "Rücksetzungs-Token anfordern", - "skipRequest": "Ich habe bereits ein Rücksetzungs-Token" - }, - "ResetPasswordForm": { - "error": "Passwort-Zurücksetzung fehlgeschlagen", - "label": { - "reset": "Passwort zurücksetzen" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/en_US/translation.json b/webclient/public/locales/en_US/translation.json deleted file mode 100644 index 3c63e0abb..000000000 --- a/webclient/public/locales/en_US/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "English", - "disconnect": "Disconnect", - "label": { - "confirmPassword": "Confirm Password", - "confirmSure": "Are you sure?", - "country": "Country", - "delete": "Delete", - "email": "Email", - "hostName": "Host Name", - "hostAddress": "Host Address", - "password": "Password", - "passwordAgain": "Password Again", - "port": "Port", - "realName": "Real Name", - "saveChanges": "Save Changes", - "token": "Token", - "username": "Username" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "Passwords don't match", - "required": "Required" - }, - "countries": { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos (Keeling) Islands", - "CD": "DR Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Add new host", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "DID YOU KNOW", - "subtitle": "<1>Cockatrice is run by volunteers<1>that love card games!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "A cross-platform virtual tabletop for multiplayer card games." - }, - "footer": { - "registerPrompt": "Not registered yet?", - "registerAction": "Create an account", - "credit": "Cockatrice is an open source project", - "version": "Version" - }, - "content": { - "subtitle1": "Play multiplayer card games online.", - "subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free." - }, - "toasts": { - "passwordResetSuccessToast": "Password Reset Successfully", - "accountActivationSuccess": "Account Activated Successfully" - } - }, - "UnsupportedContainer": { - "title": "Unsupported Browser", - "subtitle1": "Please update your browser and/or check your permissions.", - "subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features." - }, - "AccountActivationDialog": { - "title": "Account Activation", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list." - }, - "RegistrationDialog": { - "title": "Create New Account" - }, - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - }, - "ResetPasswordDialog": { - "title": "Reset Password" - }, - "AccountActivationForm": { - "error": { - "failed": "Account activation failed" - }, - "label": { - "activate": "Activate Account" - } - }, - "KnownHostForm": { - "help": "Need help adding a new host?", - "label": { - "add": "Add Host", - "find": "Find Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "Forgot Password", - "login": "Login", - "savePassword": "Save Password", - "savedPassword": "Saved Password" - } - }, - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - }, - "RequestPasswordResetForm": { - "error": "Request password reset failed", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - }, - "ResetPasswordForm": { - "error": "Password reset failed", - "label": { - "reset": "Reset Password" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/es/translation.json b/webclient/public/locales/es/translation.json deleted file mode 100644 index 33a3db45c..000000000 --- a/webclient/public/locales/es/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Español (Spanish)", - "disconnect": "Desconectar", - "label": { - "confirmPassword": "Confirmar contraseña", - "confirmSure": "¿Estás seguro?", - "country": "País", - "delete": "Borrar", - "email": "e-mail", - "hostName": "Nombre del servidor", - "hostAddress": "Dirección del servidor", - "password": "Contraseña", - "passwordAgain": "Contraseña de nuevo", - "port": "Puerto", - "realName": "Nombre real", - "saveChanges": "Guardar cambios", - "token": "Ficha", - "username": "Nombre de usuario" - }, - "validation": { - "minChars": "Se requiere un mínimo de {conteo} {conteo, plural, un {carácter} otros {caracteres}}", - "passwordsMustMatch": "Las contraseñas no coinciden", - "required": "Requerido" - }, - "countries": { - "AD": "Andorra", - "AE": "Emiratos Árabes Unidos", - "AF": "Afganistán", - "AG": "Antigua y Barbuda", - "AI": "Anguila", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antártida", - "AR": "Argentina", - "AS": "Samoa americana", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Islas Aland", - "AZ": "Azerbaiján", - "BA": "Bosnia y Herzegovina", - "BB": "Barbados", - "BD": "Bangladés", - "BE": "Bélgica", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Baréin", - "BI": "Burundi", - "BJ": "Benín", - "BL": "San Bartolomé", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, San Eustaquio y Saba", - "BR": "Brasil", - "BS": "Bahamas", - "BT": "Bután", - "BV": "Isla Bouvet", - "BW": "Botswana", - "BY": "Belorrusia", - "BZ": "Belice", - "CA": "Canadá", - "CC": "Islas (Keeling) Cocos", - "CD": "República Democrática del Congo", - "CF": "República Centroafricana", - "CG": "República del Congo", - "CH": "Suiza", - "CI": "Costa de Marfil", - "CK": "Islas Cook", - "CL": "Chile", - "CM": "Camerún", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cabo Verde", - "CW": "Curazao", - "CX": "Isla de Navidad", - "CY": "Chipre", - "CZ": "República Checa", - "DE": "Alemania", - "DJ": "Djibouti", - "DK": "Dinamarca", - "DM": "Dominica", - "DO": "República Dominicana", - "DZ": "Argelia", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egipto", - "EH": "Sáhara Occidental", - "ER": "Eritrea", - "ES": "España", - "ET": "Etiopía", - "FI": "Finlandia", - "FJ": "Fiji", - "FK": "Islas Falkland", - "FM": "Micronesia", - "FO": "Islas Faroe", - "FR": "Francia", - "GA": "Gabón", - "GB": "Reino Unido", - "GD": "Granada", - "GE": "Georgia", - "GF": "Guinea Francesa", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambía", - "GN": "Guinea", - "GP": "Guadalupe", - "GQ": "Guinea Ecuatorial", - "GR": "Grecia", - "GS": "Georgia del Sur y las islas Sandwich del sur", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bisáu", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Islas Heard y McDonald", - "HN": "Honduras", - "HR": "Croacia", - "HT": "Haití", - "HU": "Hungría", - "ID": "Indonesia", - "IE": "Irlanda", - "IL": "Israel", - "IM": "Isla de Man", - "IN": "India", - "IO": "Territorio Británico del Océano Índico", - "IQ": "Irak", - "IR": "Irán", - "IS": "Groenlandia", - "IT": "Italia", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordania", - "JP": "Japón", - "KE": "Kenya", - "KG": "Kirguistán", - "KH": "Camboya", - "KI": "Kiribati", - "KM": "Comoras", - "KN": "San Cristóbal y Nieves", - "KP": "Corea la Buena-Norte", - "KR": "Corea la Mala-Sur", - "KW": "Kuwait", - "KY": "Islas Caimán", - "KZ": "Kazajistán", - "LA": "Laos", - "LB": "Líbano", - "LC": "Santa Lucía", - "LI": "Principado de Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesoto", - "LT": "Lituania", - "LU": "Luxemburgo", - "LV": "Letonia", - "LY": "Libia", - "MA": "Marruecos", - "MC": "Mónaci", - "MD": "Moldavia", - "ME": "Montenegro", - "MF": "San Martín (Parte francesa)", - "MG": "Madagascar", - "MH": "Islas Marshall", - "MK": "Macedonia del Norte", - "ML": "Malí", - "MM": "Birmania", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Islas Marianas del Norte", - "MQ": "Martinica", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauricio", - "MV": "Maldivas", - "MW": "Malawi", - "MX": "México", - "MY": "Malasia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "Nueva Caledonia", - "NE": "Nigeria", - "NF": "Isla Norfolk", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Países Bajos (Holanda)", - "NO": "Noruega", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "Nueva Zelanda", - "OM": "Omán", - "PA": "Panamá", - "PE": "Perú", - "PF": "Polinesia Francesa", - "PG": "Papúa Nueva Guinea", - "PH": "Filipinas", - "PK": "Pakistán", - "PL": "Polonia", - "PM": "San Pedro y Miquelón", - "PN": "Islas Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestina", - "PT": "Portugal", - "PW": "Palaos", - "PY": "Paraguay", - "QA": "Catar", - "RE": "Reunión", - "RO": "Rumanía", - "RS": "Serbia", - "RU": "Rusia", - "RW": "Ruanda", - "SA": "Arabia Saudí", - "SB": "Islas Salomón", - "SC": "Islas Seychelles", - "SD": "Sudán", - "SE": "Suecia", - "SG": "Singapur", - "SH": "Santa Elena, Ascensión y Tristán de Acuña", - "SI": "Eslovenia", - "SJ": "Svalbard y Jan Mayen", - "SK": "Eslovaquia", - "SL": "Sierra Leona", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Surinam", - "SS": "Sudán del Sur", - "ST": "Santo Tomé y Príncipe", - "SV": "El Salvador", - "SX": "Sint Maarten (Países Bajos-Holanda)", - "SY": "Siria", - "SZ": "Esuatini", - "TC": "Islas Turcas y Caicos", - "TD": "Chad", - "TF": "Las Tierras Australes y Antárticas Francesas (TAAF)", - "TG": "Togo", - "TH": "Tailandia", - "TJ": "Tayikistán", - "TK": "Tokelau", - "TL": "Timor Oriental", - "TM": "Turkmenistán", - "TN": "Túnez", - "TO": "Tonga", - "TR": "Turquía", - "TT": "Trinidad y Tobago", - "TV": "Tuvalu", - "TW": "Taiwán", - "TZ": "Tanzania", - "UA": "Ucrania", - "UG": "Uganda", - "UM": "Islas Ultramarinas Menores de Estados Unidos", - "US": "Estados Unidos", - "UY": "Uruguay", - "UZ": "Uzbekistán", - "VA": "Ciudad del Vaticano", - "VC": "San Vicente y las Granadinas", - "VE": "Venezuela", - "VG": "Islas Vírgenes Británicas", - "VI": "Islas Vírgenes Estadounidenses", - "VN": "Vietnam", - "VU": "Vanuatu", - "WF": "Wallis y Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kósovo", - "ZA": "Sudáfrica", - "ZM": "Zambia", - "ZW": "Zimbabue", - "EU": "Unión Europea" - }, - "languages": { - "en-US": "Inglés - US", - "fr": "Francés", - "nl": "Holandés", - "pt_BR": " Portugués - Brasil" - } - }, - "KnownHosts": { - "label": "IP", - "add": "Añadir nueva dirección IP", - "toast": "Dirección IP correcta {modo, seleccionar, creado {creado} eliminado {eliminado} otro {editado}}." - }, - "InitializeContainer": { - "title": "¿LO SABÍAS?", - "subtitle": "<1>¡Cockatrice está buscando voluntarios<1> que amen los juegos de cartas!" - }, - "LoginContainer": { - "header": { - "title": "Conectar", - "subtitle": "Un tablero de mesa multiplataforma para juegos de cartas multijugador." - }, - "footer": { - "registerPrompt": "¿No estás registrado aún?", - "registerAction": "Crear una cuenta", - "credit": "Cockatrice es un proyecto de fuente abierta", - "version": "Versión" - }, - "content": { - "subtitle1": "Juega juegos de cartas multijugador en línea.", - "subtitle2": "Tablero multiplataforma virtual para juegos de cartas multijugador. Siempre gratuito." - }, - "toasts": { - "passwordResetSuccessToast": "Reinicio de contraseña exitoso", - "accountActivationSuccess": "Cuenta activada con éxito" - } - }, - "UnsupportedContainer": { - "title": "Navegador sin apoyo", - "subtitle1": "Por favor actualiza tu navegador y/o comprueba tus permisos.", - "subtitle2": "Nota: Los navegadores privados pueden causar que los navegadores deshabiliten ciertos permisos o funciones." - }, - "AccountActivationDialog": { - "title": "Activación de la cuenta", - "subtitle1": "Tu cuenta aún no ha sido activada.", - "subtitle2": "Necesitas comprobar el mensaje de activación recibido en el e-mail de activación." - }, - "KnownHostDialog": { - "title": "{modo, seleccionar, editar {Editar} otro {Agregar}} IP conocida", - "subtitle": "Añadir una nueva dirección IP te permite conectarte a diferentes servidores. Introduce los detalles a continuación a tu lista de IPs." - }, - "RegistrationDialog": { - "title": "Crear nueva cuenta" - }, - "RequestPasswordResetDialog": { - "title": "Requiere reinicio de contraseña" - }, - "ResetPasswordDialog": { - "title": "Reiniciar contraseña" - }, - "AccountActivationForm": { - "error": { - "failed": "La activación de la cuenta falló" - }, - "label": { - "activate": "Activar cuenta" - } - }, - "KnownHostForm": { - "help": "¿Necesitas ayuda al añadir una nueva dirección IP?", - "label": { - "add": "Añadir dirección IP", - "find": "Encontrar dirección IP" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Conectarse automáticamente", - "forgot": "Contraseña olvidada", - "login": "Conectar", - "savePassword": "Guardar contraseña", - "savedPassword": "Contraseña guardada" - } - }, - "RegisterForm": { - "label": { - "register": "Registrarse" - }, - "toast": { - "registerSuccess": "¡Te has registrado correctamente!" - } - }, - "RequestPasswordResetForm": { - "error": "Solicitud de restablecimiento de contraseña fallida", - "mfaEnabled": "El servidor tiene habilitada la autenticación multifactor", - "request": "Se requiere reiniciar el token", - "skipRequest": "Ya tengo un reinicio de token" - }, - "ResetPasswordForm": { - "error": "El reinicio de la contraseña falló", - "label": { - "reset": "Reiniciar contraseña" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/fi/translation.json b/webclient/public/locales/fi/translation.json deleted file mode 100644 index 12737731a..000000000 --- a/webclient/public/locales/fi/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Suomi (Finnish)", - "disconnect": "Katkaise yhteys", - "label": { - "confirmPassword": "Vahvista salasana", - "confirmSure": "Oletko varma?", - "country": "Maa", - "delete": "Poista", - "email": "Sähköposti", - "hostName": "Isännän Nimi", - "hostAddress": "Isännän Osoite", - "password": "Salasana", - "passwordAgain": "Salasana uudelleen", - "port": "Portti", - "realName": "Oikea nimi", - "saveChanges": "Tallenna muutokset", - "token": "Tokeni", - "username": "Käyttäjänimi" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "Passwords don't match", - "required": "Required" - }, - "countries": { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos (Keeling) Islands", - "CD": "DR Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - }, - "KnownHosts": { - "label": "Isäntä", - "add": "Lisää isäntä", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "TIESITKÖ", - "subtitle": "<1>Cockatricea pyörittävät vapaaehtoiset<1>jotka rakastavat korttipelejä!" - }, - "LoginContainer": { - "header": { - "title": "Kirjaudu sisään", - "subtitle": "Järjestelmäriippumaton virtuaalinen pöytä moninpelikorttipelejä varten." - }, - "footer": { - "registerPrompt": "Etkö ole vielä rekisteröitynyt?", - "registerAction": "Luo tili", - "credit": "Cockatrice on avoimen lähdekoodin projekti", - "version": "Versio" - }, - "content": { - "subtitle1": "Pelaa moninpelikorttipelejä verkossa.", - "subtitle2": "Järjestelmäriippumaton virtuaalinen pöytä moninpelikorttipelejä varten. Ikuisesti ilmainen." - }, - "toasts": { - "passwordResetSuccessToast": "Salasanan Nollaus Onnistui", - "accountActivationSuccess": "Tilin Aktivointi Onnistui" - } - }, - "UnsupportedContainer": { - "title": "Selainta ei tueta", - "subtitle1": "Päivitä selain ja/tai tarksita käyttöoikeudet.", - "subtitle2": "Huomautus: Yksityinen selaaminen saa jotkin selaimet poistamaan tietyt käyttöoikeudet tai ominaisuudet käytöstä." - }, - "AccountActivationDialog": { - "title": "Tilin Aktivointi", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Uuden isännän lisääminen mahdollistaa yhdistämisen eri palvelimiin. Lisää alla olevat tiedot isäntäluetteloosi." - }, - "RegistrationDialog": { - "title": "Luo Tili" - }, - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - }, - "ResetPasswordDialog": { - "title": "Nollaa Salasana" - }, - "AccountActivationForm": { - "error": { - "failed": "Tilin aktivointi epäonnistui" - }, - "label": { - "activate": "Aktivoi Tili" - } - }, - "KnownHostForm": { - "help": "Tarvitsetko apua isännän lisäämisessä?", - "label": { - "add": "Lisää Isäntä", - "find": "Etsi Isäntä" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "Forgot Password", - "login": "Kirjaudu sisään", - "savePassword": "Save Password", - "savedPassword": "Saved Password" - } - }, - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - }, - "RequestPasswordResetForm": { - "error": "Request password reset failed", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - }, - "ResetPasswordForm": { - "error": "Password reset failed", - "label": { - "reset": "Nollaa Salasana" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/fr/translation.json b/webclient/public/locales/fr/translation.json deleted file mode 100644 index aacd26d80..000000000 --- a/webclient/public/locales/fr/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Français (French)", - "disconnect": "Déconnecter", - "label": { - "confirmPassword": "Confirmer le mot de passe", - "confirmSure": "Êtes-vous sûr ?", - "country": "Pays", - "delete": "Effacer", - "email": "E-mail", - "hostName": "Nom d'hôte", - "hostAddress": "Adresse d'hôte", - "password": "Mot de passe", - "passwordAgain": "Mot de passe à nouveau", - "port": "Port", - "realName": "Nom réel", - "saveChanges": "Sauvegarder les changements", - "token": "Jeton", - "username": "Nom d'utilisateur" - }, - "validation": { - "minChars": "Minimum de {count} {count, plural, one {caractère} other {caractères}} requis", - "passwordsMustMatch": "Les mots de passe ne correspondent pas.", - "required": "Requis" - }, - "countries": { - "AD": "Andorre", - "AE": "Émirats arabes unis", - "AF": "Afghanistan", - "AG": "Antigua-et-Barbuda", - "AI": "Anguilla", - "AL": "Albanie", - "AM": "Arménie", - "AO": "Angola", - "AQ": "Antarctique", - "AR": "Argentine", - "AS": "Samoa américaines", - "AT": "Autriche", - "AU": "Australie", - "AW": "Aruba", - "AX": "Åland", - "AZ": "Azerbaijan", - "BA": "Bosnie-Herzégovine", - "BB": "Barbade", - "BD": "Bangladesh", - "BE": "Belgique", - "BF": "Burkina Faso", - "BG": "Bulgarie", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Bénin", - "BL": "Saint Barthélemy", - "BM": "Bermudes", - "BN": "Brunéi Darussalam", - "BO": "Bolivie", - "BQ": "Pays-Bas caribéens", - "BR": "Brésil", - "BS": "Bahamas", - "BT": "Bhoutan", - "BV": "Île Bouvet", - "BW": "Botswana", - "BY": "Biélorussie", - "BZ": "Belize", - "CA": "Canada", - "CC": "Îles Cocos (Keeling)", - "CD": "République Démocratique du Congo", - "CF": "République Centrafricaine", - "CG": "République du Congo", - "CH": "Suisse", - "CI": "Côte d'Ivoire", - "CK": "Îles Cook", - "CL": "Chili", - "CM": "Cameroun", - "CN": "Chine", - "CO": "Colombie", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cap-Vert", - "CW": "Curaçao", - "CX": "Île Christmas", - "CY": "Chypre", - "CZ": "Tchéquie", - "DE": "Allemagne", - "DJ": "Djibouti", - "DK": "Danemark", - "DM": "Dominique", - "DO": "République dominicaine", - "DZ": "Algérie", - "EC": "Équateur", - "EE": "Estonie", - "EG": "Égypte", - "EH": "Sahara Occidental", - "ER": "Érythrée", - "ES": "Espagne", - "ET": "Éthiopie", - "FI": "Finlande", - "FJ": "Fidji", - "FK": "Îles Falkland", - "FM": "Micronésie", - "FO": "Îles Féroé", - "FR": "France", - "GA": "Gabon", - "GB": "Royaume-Uni", - "GD": "Grenade", - "GE": "Géorgie", - "GF": "Guyane Française", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Groenland", - "GM": "Gambie", - "GN": "Guinée", - "GP": "Guadeloupe", - "GQ": "Guinée équatoriale", - "GR": "Grèce", - "GS": "Géorgie du Sud et les Îles Sandwich du Sud", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinée-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Îles Heard et McDonald", - "HN": "Honduras", - "HR": "Croatie", - "HT": "Haïti", - "HU": "Hongrie", - "ID": "Indonésie", - "IE": "Irlande", - "IL": "Israël", - "IM": "Île de Man", - "IN": "Inde", - "IO": "Territoire britannique de l'océan Indien", - "IQ": "Irak", - "IR": "Iran", - "IS": "Islande", - "IT": "Italie", - "JE": "Jersey", - "JM": "Jamaïque", - "JO": "Jordanie", - "JP": "Japon", - "KE": "Kenya", - "KG": "Kirghizistan", - "KH": "Cambodge", - "KI": "Kiribati", - "KM": "Comores", - "KN": "Saint-Kitts-et-Nevis", - "KP": "République Populaire et Démocratique de Corée", - "KR": "Corée du Sud", - "KW": "Koweït", - "KY": "Îles Caïmans", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Liban", - "LC": "Sainte-Lucie", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lituanie", - "LU": "Luxembourg", - "LV": "Lettonie", - "LY": "Lybie", - "MA": "Maroc", - "MC": "Monaco", - "MD": "Moldavie", - "ME": "Montenegro", - "MF": "Saint-Martin (Territoire Français)", - "MG": "Madagascar", - "MH": "Îles Marshall", - "MK": "Macédoine du Nord", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolie", - "MO": "Macao", - "MP": "Îles Mariannes du Nord", - "MQ": "Martinique", - "MR": "Mauritanie", - "MS": "Montserrat", - "MT": "Malte", - "MU": "Maurice", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexique", - "MY": "Malaisie", - "MZ": "Mozambique", - "NA": "Namibie", - "NC": "Nouvelle Calédonie", - "NE": "Niger", - "NF": "Île Norfolk", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Pays-Bas", - "NO": "Norvège", - "NP": "Népal", - "NR": "Nauru", - "NU": "Niué", - "NZ": "Nouvelle-Zélande", - "OM": "Oman", - "PA": "Panama", - "PE": "Pérou", - "PF": "Polynésie Française", - "PG": "Papouasie-Nouvelle-Guinée", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Pologne", - "PM": "Saint Pierre et Miquelon", - "PN": "Îles Pitcairn", - "PR": "Porto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palaos", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "La Réunion", - "RO": "Roumanie", - "RS": "Serbie", - "RU": "Russie", - "RW": "Rwanda", - "SA": "Arabie Saoudite", - "SB": "Îles Salomon", - "SC": "Seychelles", - "SD": "Soudan", - "SE": "Suède", - "SG": "Singapour", - "SH": "Sainte-Hélène", - "SI": "Slovénie", - "SJ": "Svalbard et Jan Mayen", - "SK": "Slovaquie", - "SL": "Sierra Leone", - "SM": "Saint-Marin", - "SN": "Sénégal", - "SO": "Somalie", - "SR": "Suriname", - "SS": "Soudan du Sud", - "ST": "Sao Tome et Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (territoire des Pays-Bas)", - "SY": "Syrie", - "SZ": "Eswatini", - "TC": "Îles Turks et Caïques", - "TD": "Tchad", - "TF": "Terres australes et antarctiques françaises", - "TG": "Togo", - "TH": "Thailande", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisie", - "TO": "Tonga", - "TR": "Turquie", - "TT": "Trinité-et-Tobago", - "TV": "Tuvalu", - "TW": "Taïwan", - "TZ": "Tanzanie", - "UA": "Ukraine", - "UG": "Ouganda", - "UM": "Îles mineures éloignées des États-Unis", - "US": "États-Unis", - "UY": "Uruguay", - "UZ": "Ouzbekistan", - "VA": "Saint-Siège", - "VC": "Saint-Vincent et les Grenadines", - "VE": "Venezuela", - "VG": "Îles Vierges britanniques", - "VI": "Îles Vierges des États-Unis", - "VN": "Vietnam", - "VU": "Vanuatu", - "WF": "Wallis et Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "Afrique du Sud", - "ZM": "Zambie", - "ZW": "Zimbabwe", - "EU": "Union Européenne" - }, - "languages": { - "en-US": "Anglais - US", - "fr": "Français", - "nl": "Néerlandais", - "pt_BR": "Portugais - Brésil" - } - }, - "KnownHosts": { - "label": "Hôte", - "add": "Ajouter un nouvel hôte", - "toast": "Hôte {mode, select, created {créé} deleted {effacé} other {édité}} avec succès." - }, - "InitializeContainer": { - "title": "LE SAVIEZ-VOUS ?", - "subtitle": "<1>Cockatrice est géré par des volontaires<1>qui aiment les jeux de cartes !" - }, - "LoginContainer": { - "header": { - "title": "S'identifier", - "subtitle": "Un jeu de table virtuel multi-plate-forme pour jeux de cartes multijoueurs." - }, - "footer": { - "registerPrompt": "Pas encore inscrit ?", - "registerAction": "Créer un compte", - "credit": "Cockatrice est un projet open source.", - "version": "Version" - }, - "content": { - "subtitle1": "Jouer à des jeux de cartes multijoueurs en ligne.", - "subtitle2": "Jeu de table virtuel multi-plate-forme pour jeux de cartes multijoueurs. Gratuit pour toujours." - }, - "toasts": { - "passwordResetSuccessToast": "Mot de passe réinitialisé avec succès", - "accountActivationSuccess": "Compte activé avec succès" - } - }, - "UnsupportedContainer": { - "title": "Navigateur non supporté", - "subtitle1": "Veuillez mettre à jour votre navigateur et/ou vérifier vos permissions.", - "subtitle2": "Note : La navigation privée désactive certaines permissions ou fonctionnalités." - }, - "AccountActivationDialog": { - "title": "Activation du compte", - "subtitle1": "Votre compte n'a pas encore été activé.", - "subtitle2": "Vous devez fournir le jeton d'activation reçu dans l'e-mail d'activation." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Éditer} other {Ajouter}} un hôte connu", - "subtitle": "Ajouter un nouvel hôte vous permet de vous connecter à différents serveurs. Entrez les détails ci-dessous dans votre liste d'hôtes." - }, - "RegistrationDialog": { - "title": "Créer un nouveau compte" - }, - "RequestPasswordResetDialog": { - "title": "Demander une réinitialisation du mot de passe" - }, - "ResetPasswordDialog": { - "title": "Réinitialiser le mot de passe" - }, - "AccountActivationForm": { - "error": { - "failed": "L'activation du compte a échoué." - }, - "label": { - "activate": "Activer le compte" - } - }, - "KnownHostForm": { - "help": "Besoin d'aide pour ajouter un nouvel hôte ?", - "label": { - "add": "Ajouter un hôte", - "find": "Trouver un hôte" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Connexion automatique", - "forgot": "Mot de passe oublié", - "login": "S'identifier", - "savePassword": "Enregistrer le mot de passe", - "savedPassword": "Mot de passe enregistré" - } - }, - "RegisterForm": { - "label": { - "register": "S'enregistrer" - }, - "toast": { - "registerSuccess": "Enregistrement réussi !" - } - }, - "RequestPasswordResetForm": { - "error": "La demande de réinitialisation du mot de passe a échoué.", - "mfaEnabled": "L'authentification multifacteur est activée sur ce serveur.", - "request": "Réinitialiser jeton", - "skipRequest": "Je possède déjà un jeton de réinitialisation." - }, - "ResetPasswordForm": { - "error": "La réinitialisation du mot de passe a échoué.", - "label": { - "reset": "Réinitialiser le mot de passe" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/it/translation.json b/webclient/public/locales/it/translation.json deleted file mode 100644 index 74734384b..000000000 --- a/webclient/public/locales/it/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Italiano (Italian)", - "disconnect": "Disconnetti", - "label": { - "confirmPassword": "Conferma Password", - "confirmSure": "Sei sicuro?", - "country": "Stato", - "delete": "Elimina", - "email": "Email", - "hostName": "Nome Host", - "hostAddress": "Indirizzo Host", - "password": "Password", - "passwordAgain": "Ripeti Password", - "port": "Porta", - "realName": "Nome reale", - "saveChanges": "Salva Cambiamenti", - "token": "Pedina", - "username": "Nome utente" - }, - "validation": { - "minChars": "Minimo di {%n} carattere(i) richiesti", - "passwordsMustMatch": "Le password non coincidono.", - "required": "Richiesto" - }, - "countries": { - "AD": "Andorra", - "AE": "Emirati Arabi Uniti", - "AF": "Afghanistan", - "AG": "Antigua e Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antartide", - "AR": "Argentina", - "AS": "Samoa Americane", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Isole Åland", - "AZ": "Azerbaijan", - "BA": "Bosnia ed Erzegovina", - "BB": "Barbados", - "BD": "Bangledesh", - "BE": "Belgio", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrein", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei", - "BO": "Bolivia", - "BQ": "Paesi Bassi Caraibici", - "BR": "Brasile", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Isola Bouvet", - "BW": "Botswana", - "BY": "Bielorussia", - "BZ": "Belize", - "CA": "Canada", - "CC": "Isole Cocos (Keeling)", - "CD": "DR Congo", - "CF": "Repubblica Centrafricana", - "CG": "Repubblica del Congo", - "CH": "Svizzera", - "CI": "Costa D'Avorio", - "CK": "Isole Cook", - "CL": "Cile", - "CM": "Camerun", - "CN": "Cina", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Capo Verde", - "CW": "Curaçao", - "CX": "Isola Christmas", - "CY": "Cipro", - "CZ": "Repubblica Ceca", - "DE": "Germania", - "DJ": "Gibuti", - "DK": "Danimarca", - "DM": "Dominica", - "DO": "Repubblica Domenicana", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egitto", - "EH": "Sahara Occidentale", - "ER": "Eritrea", - "ES": "Spagna", - "ET": "Etiopia", - "FI": "Finlandia", - "FJ": "Fiji", - "FK": "Isole Falkland", - "FM": "Micronesia", - "FO": "Isole Faroe", - "FR": "Francia", - "GA": "Gabon", - "GB": "Regno Unito", - "GD": "Grenada", - "GE": "Georgia", - "GF": "Guyana Francese", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Groenlandia", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadalupa", - "GQ": "Guinea Equatoriale", - "GR": "Grecia", - "GS": "Georgia del Sud e Isole Sandwich Australi", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Isole Heard e McDonald", - "HN": "Honduras", - "HR": "Croazia", - "HT": "Haiti", - "HU": "Ungheria", - "ID": "Indonesia", - "IE": "Irlanda", - "IL": "Israele", - "IM": "Isola di Man", - "IN": "India", - "IO": "Territorio britannico dell'Oceano Indiano", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Islanda", - "IT": "Italia", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Giordania", - "JP": "Giappone", - "KE": "Kenya", - "KG": "Kirghizistan", - "KH": "Cambogia", - "KI": "Kiribati", - "KM": "Comore", - "KN": "Saint Kitts e Nevis", - "KP": "Korea del Nord", - "KR": "Korea del Sud", - "KW": "Kuwait", - "KY": "Isole Cayman", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebano", - "LC": "Santa Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lituania", - "LU": "Lussemburgo", - "LV": "Lettonia", - "LY": "Libia", - "MA": "Marocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "San Martino (parte Francese)", - "MG": "Madagascar", - "MH": "Isole Marshall", - "MK": "Macedonia del Nord", - "ML": "Mali", - "MM": "Birmania", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Isole Marianne Settentrionali", - "MQ": "Martinica", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldive", - "MW": "Malawi", - "MX": "Messico", - "MY": "Malesia", - "MZ": "Mozambico", - "NA": "Namibia", - "NC": "Nuova Caledonia", - "NE": "Niger", - "NF": "Isola Norfolk", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Paesi Bassi", - "NO": "Norvegia", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "Nuova Zelanda", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "Polinesia Francese", - "PG": "Papua Nuova Guinea", - "PH": "Filippine", - "PK": "Pakistan", - "PL": "Polonia", - "PM": "Saint Pierre e Miquelon", - "PN": "Pitcairn", - "PR": "Porto Rico", - "PS": "Palestina", - "PT": "Portogallo", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "La Riunione", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Arabia Saudita", - "SB": "Isole Salomone", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Svezia", - "SG": "Singapore", - "SH": "Sant'Elena, Ascensione e Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard e Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "Sud Sudan", - "ST": "Sao Tome e Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Parte Olandese)", - "SY": "Siria", - "SZ": "eSwatini", - "TC": "Turks e Caicos", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor Est", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turchia", - "TT": "Trinidad e Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ucraina", - "UG": "Uganda", - "UM": "Isole minori esterne degli Stati Uniti d'America", - "US": "Stati Uniti", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Vaticano", - "VC": "Saint Vincent e Grenadine", - "VE": "Venezuela", - "VG": "Isole Vergini britanniche", - "VI": "Isole Vergini americane", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis e Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "Sud Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "Unione Europea" - }, - "languages": { - "en-US": "Inglese - US", - "fr": "Francese", - "nl": "Olandese", - "pt_BR": "Portoghese - Brasile" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Aggiungi nuovo Host", - "toast": "Host {mode, select, created {creato} deleted {eliminato} other {modificato}} con successo." - }, - "InitializeContainer": { - "title": "LO SAPEVI", - "subtitle": "<1>Cockatrice è gestita da volontari<1>che amano i giochi di carte!" - }, - "LoginContainer": { - "header": { - "title": "Accesso", - "subtitle": "Un portale digitale multi-piattaforma per giochi di carte multigiocatore" - }, - "footer": { - "registerPrompt": "Non sei ancora registrato?", - "registerAction": "Crea un nuovo account", - "credit": "Cockatrice è un progetto open source", - "version": "Versione" - }, - "content": { - "subtitle1": "Gioca online giochi di carte multigiocatore.", - "subtitle2": "Portale digitale multi-piattaforma per giochi di carte multigiocatore. Sempre gratis." - }, - "toasts": { - "passwordResetSuccessToast": "Password reimpostata con successo", - "accountActivationSuccess": "Account attivato con successo" - } - }, - "UnsupportedContainer": { - "title": "Browser non supportato", - "subtitle1": "Aggiornare il browser e/o controllare i permessi.", - "subtitle2": "Nota: la navigazione Privata può far si che il browser disabiliti certi permessi o funzionalità." - }, - "AccountActivationDialog": { - "title": "Attivazione dell'account", - "subtitle1": "Il tuo account non è ancora stato attivato.", - "subtitle2": "È necessario fornire il codice ricevuto nell'e-mail di attivazione." - }, - "KnownHostDialog": { - "title": "{modalità, seleziona, modifica {Modifica} altro {Aggiungi}} Host conosciuto", - "subtitle": "L'aggiunta di un nuovo host consente di connettersi a server diversi. Inserisci i dettagli di seguito nella lista degli host." - }, - "RegistrationDialog": { - "title": "Crea un nuovo account" - }, - "RequestPasswordResetDialog": { - "title": "Richiedi la reimpostazione della password" - }, - "ResetPasswordDialog": { - "title": "Reimposta Password" - }, - "AccountActivationForm": { - "error": { - "failed": "Attivazione dell'account non riuscita" - }, - "label": { - "activate": "Attiva account" - } - }, - "KnownHostForm": { - "help": "Hai bisogno di aiuto per aggiungere un nuovo host?", - "label": { - "add": "Aggiungi Host", - "find": "Trova Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Connessione automatica", - "forgot": "Password dimenticata", - "login": "Accesso", - "savePassword": "Salva Password", - "savedPassword": "Password salvata" - } - }, - "RegisterForm": { - "label": { - "register": "Iscriviti" - }, - "toast": { - "registerSuccess": "Iscrizione completata correttamente!" - } - }, - "RequestPasswordResetForm": { - "error": "Richiesta reimpostazione password non riuscita", - "mfaEnabled": "Il server ha l'autenticazione a più fattori abilitata", - "request": "Richiedi il token di ripristino", - "skipRequest": "Ho già un token di ripristino" - }, - "ResetPasswordForm": { - "error": "Reimpostazione della password non riuscita", - "label": { - "reset": "Reimposta Password" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/nl/translation.json b/webclient/public/locales/nl/translation.json deleted file mode 100644 index fffb18989..000000000 --- a/webclient/public/locales/nl/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Nederlands (Dutch)", - "disconnect": "Verbinding Verbreken", - "label": { - "confirmPassword": "Wachtwoord Bevestigen", - "confirmSure": "Weet je het zeker?", - "country": "Land", - "delete": "Verwijderen", - "email": "Email", - "hostName": "Hostnaam", - "hostAddress": "Hostaddres", - "password": "Wachtwoord", - "passwordAgain": "Wachtwoord Nogmaals", - "port": "Poort", - "realName": "Echte Naam", - "saveChanges": "Wijzigingen Opslaan", - "token": "Token", - "username": "Gebruikersnaam" - }, - "validation": { - "minChars": "Minimum van {count} {count, plural, one {karakter} other {karakters}} verplicht", - "passwordsMustMatch": "Wachtwoorden komen niet overeen", - "required": "Verplicht" - }, - "countries": { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos (Keeling) Islands", - "CD": "DR Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Voeg nieuwe host toe", - "toast": "Host {mode, select created {toegevoegd} deleted {verwijderd} other {aangepast}}." - }, - "InitializeContainer": { - "title": "WIST JE DAT", - "subtitle": "<1>Cockatrice word gerund door vrijwilligers<1>die van kaartspellen houden!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "Een cross-platform virtual tabletop voor multiplayer kaartspellen." - }, - "footer": { - "registerPrompt": "Nog niet geregistreerd?", - "registerAction": "Maak een account aan", - "credit": "Cockatrice is een open source project", - "version": "Versie" - }, - "content": { - "subtitle1": "Speel multiplayer kaartspellen online.", - "subtitle2": "Cross-platform virtual tabletop voor multiplayer kaartspellen. Gratis en open source." - }, - "toasts": { - "passwordResetSuccessToast": "Wachtwoord Opnieuw Ingesteld", - "accountActivationSuccess": "Account Succesvol Geactiveerd" - } - }, - "UnsupportedContainer": { - "title": "Niet-ondersteunde Browser", - "subtitle1": "Update je browser en/of check je instellingen.", - "subtitle2": "Let op: Private browsing zorgt er voor dat sommige browsers bepaalde toestemmingen of functies uitschakelen." - }, - "AccountActivationDialog": { - "title": "Account Activatie", - "subtitle1": "Je account is nog niet geactiveerd.", - "subtitle2": "Vul de activerings-token die je hebt ontvangen via de activerings-email in." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Aanpassen} other {Toevoegen}} Bekende Host", - "subtitle": "Door een nieuwe host toe te voegen kun je verbinden met andere servers. Vul hieronder de gegevens in." - }, - "RegistrationDialog": { - "title": "Maak Een Account Aan" - }, - "RequestPasswordResetDialog": { - "title": "Opniew Wachtwoord Aanvragen" - }, - "ResetPasswordDialog": { - "title": "Vraag Wachtwoord Opnieuw Aan" - }, - "AccountActivationForm": { - "error": { - "failed": "Activering account mislukt" - }, - "label": { - "activate": "Activeer Account" - } - }, - "KnownHostForm": { - "help": "Hulp nodig met het toevoegen van een nieuwe host?", - "label": { - "add": "Voeg Host Toe", - "find": "Vind Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Verbind Automatisch", - "forgot": "Wachtwoord Vergeten", - "login": "Log in", - "savePassword": "Wachtwoord Opslaan", - "savedPassword": "Opgeslagen Wachtwoord" - } - }, - "RegisterForm": { - "label": { - "register": "Registreer" - }, - "toast": { - "registerSuccess": "Registratie Geslaagd!" - } - }, - "RequestPasswordResetForm": { - "error": "Opniew wachtwoord aanvragen mislukt", - "mfaEnabled": "Server heeft multi-factor authenticatie ingeschakeld", - "request": "Vraag Reset Token Aan", - "skipRequest": "Ik heb al een reset token" - }, - "ResetPasswordForm": { - "error": "Opnieuw wachtwoord aanvragen mislukt", - "label": { - "reset": "Vraag Wachtwoord Opnieuw Aan" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/pl/translation.json b/webclient/public/locales/pl/translation.json deleted file mode 100644 index abe400792..000000000 --- a/webclient/public/locales/pl/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Polski (Polish)", - "disconnect": "Rozłącz", - "label": { - "confirmPassword": "Potwierdź Hasło", - "confirmSure": "Czy na pewno?", - "country": "Kraj", - "delete": "Usuń", - "email": "Email", - "hostName": "Nazwa Hosta", - "hostAddress": "Adres Hosta", - "password": "Hasło", - "passwordAgain": "Hasło Ponownie:", - "port": "Port", - "realName": "Prawdziwe Imię", - "saveChanges": "Zapisz zmiany", - "token": "Token", - "username": "Nazwa użytkownika" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "Hasła nie są zgodne.", - "required": "Wymagane" - }, - "countries": { - "AD": "Andora", - "AE": "Zjednoczone Emiraty Arabskie", - "AF": "Afganistan", - "AG": "Antigua i Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarktyka", - "AR": "Argentyna", - "AS": "Samoa Amerykańskie", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Wyspy Alandzkie", - "AZ": "Azerbejdżan", - "BA": "Bośnia i Hercegowina", - "BB": "Barbados", - "BD": "Bangladesz", - "BE": "Belgia", - "BF": "Burkina Faso", - "BG": "Bułgaria", - "BH": "Bahrajn", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint-Barthélemy", - "BM": "Bermudy", - "BN": "Brunei", - "BO": "Boliwia", - "BQ": "Bonaire, Sint Eustatius i Saba", - "BR": "Brazylia", - "BS": "Bahamy", - "BT": "Bhutan", - "BV": "Wyspa Bouveta", - "BW": "Botwsana", - "BY": "Białoruś", - "BZ": "Belize", - "CA": "Kanada", - "CC": "Wyspy Kokosowe", - "CD": "Demokratyczna Republika Kongo", - "CF": "Republika Środkowoafrykańska", - "CG": "Kongo", - "CH": "Szwajcaria", - "CI": "Wybrzeże Kości Słoniowej", - "CK": "Wyspa Cooka", - "CL": "Chile", - "CM": "Kamerun", - "CN": "Chiny", - "CO": "Kolumbia", - "CR": "Kostaryka", - "CU": "Kuba", - "CV": "Republika Zielonego Przylądka", - "CW": "Curaçao", - "CX": "Wyspa Bożego Narodzenia", - "CY": "Cypr", - "CZ": "Czechy", - "DE": "Niemcy", - "DJ": "Dżibuti", - "DK": "Dania", - "DM": "Dominika", - "DO": "Dominikana", - "DZ": "Algieria", - "EC": "Ekwador", - "EE": "Estonia", - "EG": "Egipt", - "EH": "Sahara Zachodnia", - "ER": "Erytrea", - "ES": "Hiszpania", - "ET": "Etiopia", - "FI": "Finlandia", - "FJ": "Fidżi", - "FK": "Falklandy", - "FM": "Mikronezja", - "FO": "Wyspy Owcze", - "FR": "Francja", - "GA": "Gabon", - "GB": "Wielka Brytania", - "GD": "Grenada", - "GE": "Gruzja", - "GF": "Gujana Francuska", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Grenlandia", - "GM": "Gambia", - "GN": "Gwinea", - "GP": "Gwadelupa", - "GQ": "Gwinea Równikowa", - "GR": "Grecja", - "GS": "Georgia Południowa i Sandwich Południowy", - "GT": "Gwatemala", - "GU": "Guam", - "GW": "Gwinea Bissau", - "GY": "Gujana", - "HK": "Hongkong", - "HM": "Wyspy Heard i McDonalda", - "HN": "Honduras", - "HR": "Chorwacja", - "HT": "Haiti", - "HU": "Węgry", - "ID": "Indonezja", - "IE": "Irlandia", - "IL": "Israel", - "IM": "Wyspa Man", - "IN": "Indie", - "IO": "Brytyjskie Terytorium Oceanu Indyjskiego", - "IQ": "Irak", - "IR": "Iran", - "IS": "Islandia", - "IT": "Włochy", - "JE": "Jersey", - "JM": "Jamajka", - "JO": "Jordania", - "JP": "Japonia", - "KE": "Kenia", - "KG": "Kirgistan", - "KH": "Kambodża", - "KI": "Kiribati", - "KM": "Komory", - "KN": "Saint Kitts i Nevis", - "KP": "Korea Północna", - "KR": "Korea Południowa", - "KW": "Kuwejt", - "KY": "Kajmany", - "KZ": "Kazachstan", - "LA": "Laos", - "LB": "Liban", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Litwa", - "LU": "Luksemburg", - "LV": "Łotwa", - "LY": "Libia", - "MA": "Maroko", - "MC": "Monako", - "MD": "Mołdawia", - "ME": "Czarnogóra", - "MF": "Saint-Martin", - "MG": "Madagaskar", - "MH": "Wyspy Marshalla", - "MK": "Macedonia Północna", - "ML": "Mali", - "MM": "Mjanma", - "MN": "Mongolia", - "MO": "Makau", - "MP": "Mariany Północne", - "MQ": "Martynika", - "MR": "Mauretania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Malediwy", - "MW": "Malawi", - "MX": "Meksyk", - "MY": "Malezja", - "MZ": "Mozambik", - "NA": "Namibia", - "NC": "Nowa Kaledonia", - "NE": "Niger", - "NF": "Wyspa Norfolk", - "NG": "Nigeria", - "NI": "Nikaragua", - "NL": "Holandia", - "NO": "Norwegia", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "Nowa Zelandia", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "Polinezja Francuska", - "PG": "Papua-Nowa Gwinea", - "PH": "Filipiny", - "PK": "Pakistan", - "PL": "Polska", - "PM": "Saint-Pierre i Miquelon", - "PN": "Pitcairn", - "PR": "Portoryko", - "PS": "Palestyna", - "PT": "Portugalia", - "PW": "Palau", - "PY": "Paragwaj", - "QA": "Katar", - "RE": "Reunion", - "RO": "Rumunia", - "RS": "Serbia", - "RU": "Rosja", - "RW": "Rwanda", - "SA": "Arabia Saudyjska", - "SB": "Wyspy Solomona", - "SC": "Seszele", - "SD": "Sudan", - "SE": "Szwecja", - "SG": "Singapur", - "SH": "Wyspa Świętej Heleny, Wyspa Wniebowstąpienia i Tristan da Cunha", - "SI": "Słowenia", - "SJ": "Svalbard i Jan Mayen", - "SK": "Słowacja", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Surinam", - "SS": "Sudan Południowy", - "ST": "Wyspy Świętego Tomasza i Książęca", - "SV": "Salwador", - "SX": "Sint Maarten", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks i Caicos", - "TD": "Czad", - "TF": "FTPA", - "TG": "Togo", - "TH": "Tajlandia", - "TJ": "Tadżykistan", - "TK": "Tokelau", - "TL": "Timor Wschodni", - "TM": "Turkmenistan", - "TN": "Tunezja", - "TO": "Tonga", - "TR": "Turcja", - "TT": "Trynidad i Tobago", - "TV": "Tuvalu", - "TW": "Tajwan", - "TZ": "Tanzania", - "UA": "Ukraina", - "UG": "Uganda", - "UM": "Dalekie Wyspy Mniejsze Stanów Zjednoczonych", - "US": "Stany Zjednoczone", - "UY": "Urugwaj", - "UZ": "Uzbekistan", - "VA": "Watykan", - "VC": "Saint Vincent i Grenadyny", - "VE": "Wenezuela", - "VG": "Brytyjskie Wyspy Dziewicze", - "VI": "Wyspy Dziewicze Stanów Zjednoczonych", - "VN": "Wietnam", - "VU": "Vanuatu", - "WF": "Wallis i Futuna", - "WS": "Samoa", - "YE": "Jemen", - "YT": "Majotta", - "XK": "Kosowo", - "ZA": "Południowa Afryka", - "ZM": "Zambia", - "ZW": "Zimbambwe", - "EU": "Unia Europejska" - }, - "languages": { - "en-US": "Angielski - USA", - "fr": "Francuski", - "nl": "Holenderski", - "pt_BR": "Portugalski - Brazylia" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Dodaj nowego hosta", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "WIEDZIAŁEŚ", - "subtitle": "<1>Cockatrice jest prowadzony przez ochotników<1>którzy uwielbiają karcianki!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "Wieloplatformowy wirtualny stół do wieloosobowych gier karcianych." - }, - "footer": { - "registerPrompt": "Jeszcze nie zarejestrowany?", - "registerAction": "Stwórz konto", - "credit": "Cockatrice jest projektem open source", - "version": "Wersja" - }, - "content": { - "subtitle1": "Graj wieloosobowe karcianki online.", - "subtitle2": "Wieloplatformowy wirtualny stół do wieloosobowych gier karcianych. Zawsze darmowy i wolny." - }, - "toasts": { - "passwordResetSuccessToast": "Hasło Pomyślnie Zresetowany", - "accountActivationSuccess": "Konto Pomyślnie Aktywowane" - } - }, - "UnsupportedContainer": { - "title": "Nieobsługiwana Przeglądarka", - "subtitle1": "Proszę zaktualizuj swoją przeglądarkę i/lub sprawdź jej uprawnienia.", - "subtitle2": "Przeglądanie prywatne może wyłączyć niektóre uprawnienia lub funkcje." - }, - "AccountActivationDialog": { - "title": "Aktywacja Konta", - "subtitle1": "Twoje konto nie zostało jeszcze aktywowane.", - "subtitle2": "Musisz podać token aktywujący, otrzymany w wiadomości email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Dodanie nowego hosta pozwoli Ci połączyć się z innymi serwerami. Podaj jego dane poniżej aby dodać go do listy." - }, - "RegistrationDialog": { - "title": "Stwórz Nowe Konto" - }, - "RequestPasswordResetDialog": { - "title": "Żądanie Resetu Hasła" - }, - "ResetPasswordDialog": { - "title": "Zresetuj hasło" - }, - "AccountActivationForm": { - "error": { - "failed": "Aktywacja konta zakończona niepowodzeniem" - }, - "label": { - "activate": "Aktywuj Konto" - } - }, - "KnownHostForm": { - "help": "Potrzebujesz pomocy z dodaniem nowego hosta?", - "label": { - "add": "Dodaj Hosta", - "find": "Znajdź Hosta" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Połącz automatycznie", - "forgot": "Zapomniałem hasła", - "login": "Login", - "savePassword": "Zapisz hasło", - "savedPassword": "Zapisane Hasło" - } - }, - "RegisterForm": { - "label": { - "register": "Zarejestruj się" - }, - "toast": { - "registerSuccess": "Rejestracja powiodła się!" - } - }, - "RequestPasswordResetForm": { - "error": "Żądanie resetu hasła nie powiodło się.", - "mfaEnabled": "Serwer ma włączone uwierzytelnianie wielkoskładnikowe", - "request": "Zażądaj Token Resetu", - "skipRequest": "Mam już token resetu" - }, - "ResetPasswordForm": { - "error": "Żądanie resetu hasła nie powiodło się.", - "label": { - "reset": "Zresetuj hasło" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/pt_BR/translation.json b/webclient/public/locales/pt_BR/translation.json deleted file mode 100644 index 10ad08603..000000000 --- a/webclient/public/locales/pt_BR/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Português do Brasil (Brazilian Portuguese)", - "disconnect": "Desconectar", - "label": { - "confirmPassword": "Confirmar senha", - "confirmSure": "Você tem certeza?", - "country": "País", - "delete": "Excluir", - "email": "Email", - "hostName": "Nome do host", - "hostAddress": "Endereço do host", - "password": "Senha", - "passwordAgain": "Senha novamente", - "port": "Porta", - "realName": "Nome Real", - "saveChanges": "Salvar Mudanças", - "token": "Ficha", - "username": "Nome de usuário" - }, - "validation": { - "minChars": "Mínimo de {count} {count, plural, one {caractere} other {caracteres}} necessários", - "passwordsMustMatch": "As senhas não correspondem", - "required": "Campo obrigatório" - }, - "countries": { - "AD": "Andorra", - "AE": "Emirados Árabes Unidos", - "AF": "Afeganistão", - "AG": "Antígua e Barbuda", - "AI": "Anguila", - "AL": "Albânia", - "AM": "Armênia", - "AO": "Angola", - "AQ": "Antártica", - "AR": "Argentina", - "AS": "Samoa Americana", - "AT": "Áustria", - "AU": "Austrália", - "AW": "Aruba", - "AX": "Ilhas Aland", - "AZ": "Azerbaijão", - "BA": "Bósnia e Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Bélgica", - "BF": "Burquina Faso", - "BG": "Bulgária", - "BH": "Bahrein", - "BI": "Burundi", - "BJ": "Benim", - "BL": "São Bartolomeu", - "BM": "Bermudas", - "BN": "Brunei", - "BO": "Bolivia", - "BQ": "Bonaire, Santo Eustáquio e Saba", - "BR": "Brasil", - "BS": "Bahamas", - "BT": "Butão", - "BV": "Ilha Bouvet", - "BW": "Botsuana", - "BY": "Bielorrússia", - "BZ": "Belize", - "CA": "Canadá", - "CC": "Ilhas Cocos (Keeling)", - "CD": "RD Congo", - "CF": "República Centro-Africana", - "CG": "República do Congo", - "CH": "Suiça", - "CI": "Costa do Marfim", - "CK": "Ilhas Cook", - "CL": "Chile", - "CM": "Camarões", - "CN": "China", - "CO": "Colômbia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cabo Verde", - "CW": "Curaçao", - "CX": "Ilha Christmas", - "CY": "Chipre", - "CZ": "Tchéquia", - "DE": "Alemanha", - "DJ": "Djibuti", - "DK": "Dinamarca", - "DM": "Dominica", - "DO": "República Dominicana", - "DZ": "Argélia", - "EC": "Equador", - "EE": "Estônia", - "EG": "Egito", - "EH": "Saara Ocidental", - "ER": "Eritreia", - "ES": "Espanha", - "ET": "Etiópia", - "FI": "Finlândia", - "FJ": "Fiji", - "FK": "Ilhas Malvinas", - "FM": "Micronésia", - "FO": "Ilhas Faroé", - "FR": "França", - "GA": "Gabão", - "GB": "Reino Unido", - "GD": "Granada", - "GE": "Geórgia", - "GF": "Guiana Francesa", - "GG": "Guernsey", - "GH": "Gana", - "GI": "Gibraltar", - "GL": "Groenlândia", - "GM": "Gâmbia", - "GN": "Guiné", - "GP": "Guadalupe", - "GQ": "Guiné Equatorial", - "GR": "Grécia", - "GS": "Ilhas Geórgia do Sul e Sandwich do Sul", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guiné-Bissau", - "GY": "Guiana", - "HK": "Hong Kong", - "HM": "Ilha Head e Ilhas McDonald", - "HN": "Honduras", - "HR": "Croácia", - "HT": "Haiti", - "HU": "Hungria", - "ID": "Indonésia", - "IE": "Irlanda", - "IL": "Israel", - "IM": "Ilha de Man", - "IN": "Índia", - "IO": "Território Britânico do Oceano Índico", - "IQ": "Iraque", - "IR": "Irã", - "IS": "Islândia", - "IT": "Itália", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordânia", - "JP": "Japão", - "KE": "Quênia", - "KG": "Quirguistão", - "KH": "Cambodja", - "KI": "Quiribati", - "KM": "Comores", - "KN": "São Cristóvão e Névis", - "KP": "Coréia do Norte", - "KR": "Coreia do Sul", - "KW": "Kuwait", - "KY": "Ilhas Cayman", - "KZ": "Cazaquistão", - "LA": "Laos", - "LB": "Líbano", - "LC": "Santa Lúcia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Libéria", - "LS": "Lesoto", - "LT": "Lituânia", - "LU": "Luxemburgo", - "LV": "Letônia", - "LY": "Líbia", - "MA": "Marrocos", - "MC": "Mônaco", - "MD": "Moldávia", - "ME": "Montenegro", - "MF": "São Martinho (França)", - "MG": "Madagáscar", - "MH": "Ilhas Marshall", - "MK": "Macedônia do Norte", - "ML": "Mali", - "MM": "Mianmar", - "MN": "Mongólia", - "MO": "Macau", - "MP": "Ilhas Marianas do Norte", - "MQ": "Martinica", - "MR": "Mauritânia", - "MS": "Monserrate", - "MT": "Malta", - "MU": "Ilhas Maurício", - "MV": "Maldivas", - "MW": "Malawi", - "MX": "México", - "MY": "Malásia", - "MZ": "Moçambique", - "NA": "Namíbia", - "NC": "Nova Caledônia", - "NE": "Níger", - "NF": "Ilha Norfolk", - "NG": "Nigéria", - "NI": "Nicarágua", - "NL": "Holanda", - "NO": "Noruega", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "Nova Zelândia", - "OM": "Omã", - "PA": "Panamá", - "PE": "Peru", - "PF": "Polinésia Francesa", - "PG": "Papua Nova Guiné", - "PH": "Filipinas", - "PK": "Paquistão", - "PL": "Polônia", - "PM": "Saint-Pierre e Miquelon", - "PN": "Ilhas Pitcairn", - "PR": "Porto Rico", - "PS": "Palestina", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguai", - "QA": "Catar", - "RE": "Ilha da Reunião", - "RO": "Romênia", - "RS": "Sérvia", - "RU": "Rússia", - "RW": "Ruanda", - "SA": "Arábia Saudita", - "SB": "Ilhas Salomão", - "SC": "Seychelles", - "SD": "Sudão", - "SE": "Suécia", - "SG": "Cingapura", - "SH": "Santa Helena, Ascensão e Tristão da Cunha", - "SI": "Eslovênia", - "SJ": "Svalbard e Jan Mayen", - "SK": "Eslováquia", - "SL": "Serra Leoa", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somália", - "SR": "Suriname", - "SS": "Sudão do Sul", - "ST": "São Tomé e Príncipe", - "SV": "El Salvador", - "SX": "São Martinho (Holanda)", - "SY": "Síria", - "SZ": "Essuatíni", - "TC": "Ilhas Turcas e Caicos", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Tailândia", - "TJ": "Tajiquistão", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turcomenistão", - "TN": "Tunísia", - "TO": "Tonga", - "TR": "Turquia", - "TT": "Trindade e Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzânia", - "UA": "Ucrânia", - "UG": "Uganda", - "UM": "Ilhas Menores Distantes dos Estados Unidos", - "US": "Estados Unidos", - "UY": "Uruguai", - "UZ": "Uzbequistão", - "VA": "Santa Sé", - "VC": "São Vicente e Granadinas", - "VE": "Venezuela", - "VG": "Ilhas Virgens Britânicas", - "VI": "Ilhas Virgens Americanas", - "VN": "Vietnã", - "VU": "Vanuatu", - "WF": "Wallis e Futuna", - "WS": "Samoa", - "YE": "Iêmen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "África do Sul", - "ZM": "Zâmbia", - "ZW": "Zimbábue", - "EU": "União Européia" - }, - "languages": { - "en-US": "Inglês - EUA", - "fr": "Francês", - "nl": "Holandês", - "pt_BR": "Português - Brasil" - } - }, - "KnownHosts": { - "label": "Servidor", - "add": "Adicionar novo servidor", - "toast": "Servidor {mode, select, created {criado} deleted {deletado} other {editado}} com sucesso." - }, - "InitializeContainer": { - "title": "VOCÊ SABIA?", - "subtitle": "<1>O Cockatrice é administrado por voluntários<1>que adoram jogos de cartas!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "Uma mesa virtual multiplataforma para jogos multijogador de carta." - }, - "footer": { - "registerPrompt": "Não registrou ainda?", - "registerAction": "Crie uma conta", - "credit": "Cockatrice é um projeto de código aberto", - "version": "Versão" - }, - "content": { - "subtitle1": "Jogue jogos multijogador de cartas online.", - "subtitle2": "Mesa virtual multiplataforma para jogos multijogador de carta. Gratuito para sempre." - }, - "toasts": { - "passwordResetSuccessToast": "Senha redefinida com sucesso", - "accountActivationSuccess": "Conta ativada com sucesso" - } - }, - "UnsupportedContainer": { - "title": "Navegador não suportado", - "subtitle1": "Por favor atualize seu navegador de internet e/ou check as permissões", - "subtitle2": "Observação: a navegação privada faz com que alguns navegadores desativem determinadas permissões ou recursos." - }, - "AccountActivationDialog": { - "title": "Ativação da conta", - "subtitle1": "Sua conta ainda não foi ativada.", - "subtitle2": "Você precisa fornecer o código de ativação recebido por email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Editar} other {Adicionar}} servidor conhecido", - "subtitle": "Adicionar um novo host permite que você se conecte a diferentes servidores. Insira os detalhes abaixo em sua lista de hosts." - }, - "RegistrationDialog": { - "title": "Criar Nova Conta" - }, - "RequestPasswordResetDialog": { - "title": "Solicitar redefinição de senha" - }, - "ResetPasswordDialog": { - "title": "Redefinir Senha" - }, - "AccountActivationForm": { - "error": { - "failed": "Falha na activação de conta" - }, - "label": { - "activate": "Ativar Conta" - } - }, - "KnownHostForm": { - "help": "Necessita de ajuda para adicionar um novo host?", - "label": { - "add": "Adicionar Host", - "find": "Buscar Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Conectar Automaticamente", - "forgot": "Esqueci minha senha", - "login": "Login", - "savePassword": "Salvar Senha", - "savedPassword": "Salvar Senha" - } - }, - "RegisterForm": { - "label": { - "register": "Registrar" - }, - "toast": { - "registerSuccess": "Registro bem-sucedido" - } - }, - "RequestPasswordResetForm": { - "error": "Falha na solicitação de redefinição de senha", - "mfaEnabled": "O servidor tem autenticação de multi-fator ativada", - "request": "Solicitar código de redefinição", - "skipRequest": "Eu já tenho um código de redefinição" - }, - "ResetPasswordForm": { - "error": "Falha na redefinição de senha", - "label": { - "reset": "Redefinir Senha" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/ru/translation.json b/webclient/public/locales/ru/translation.json deleted file mode 100644 index 5e92f790e..000000000 --- a/webclient/public/locales/ru/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Русский (Russian)", - "disconnect": "Прервать подключение", - "label": { - "confirmPassword": "Подтверждение пароля:", - "confirmSure": "Вы уверены?", - "country": "Страна:", - "delete": "Удалить", - "email": "Адрес email:", - "hostName": "Наименование сервера", - "hostAddress": "Адрес сервера", - "password": "Пароль:", - "passwordAgain": "Подтверждение пароля:", - "port": "&Порт:", - "realName": "Настоящее имя:", - "saveChanges": "Сохранить изменения", - "token": "Фишка", - "username": "Имя пользователя:" - }, - "validation": { - "minChars": "Как минимум {count} {count, plural, one {character} other {characters}} необходимо", - "passwordsMustMatch": "Введенные пароли не совпадают.", - "required": "Необходимо" - }, - "countries": { - "AD": "Андорра", - "AE": "ОАЭ", - "AF": "Афганистан", - "AG": "Антигуа и Барбуда", - "AI": "Ангилья", - "AL": "Албания", - "AM": "Армения", - "AO": "Ангола", - "AQ": "Антарктика", - "AR": "Аргентина", - "AS": "Американское Самоа", - "AT": "Австрия", - "AU": "Австралия", - "AW": "Аруба", - "AX": "Аландские острова", - "AZ": "Азербайджан", - "BA": "Босния и Герцеговина", - "BB": "Барбадос", - "BD": "Бангладеш", - "BE": "Бельгия", - "BF": "Буркина Фасо", - "BG": "Болгария", - "BH": "Бахрейн", - "BI": "Бурунди", - "BJ": "Бенин", - "BL": "Сен-Бартелеми", - "BM": "Бермуды", - "BN": "Бруней", - "BO": "Боливия", - "BQ": "Бонэйр", - "BR": "Бразилия", - "BS": "Багамы", - "BT": "Бутан", - "BV": "Остров Буве", - "BW": "Ботсвана", - "BY": "Беларусь", - "BZ": "Белиз", - "CA": "Канада", - "CC": "Кокосовые острова", - "CD": "ДР Конго", - "CF": "ЦАР", - "CG": "Республика Конго", - "CH": "Швейцария", - "CI": "Кот-д'Ивуар", - "CK": "Острова Кука", - "CL": "Чили", - "CM": "Камерун", - "CN": "Китай", - "CO": "Колумбия", - "CR": "Коста-Рика", - "CU": "Куба", - "CV": "Кабо-Верде", - "CW": "Кюрасао", - "CX": "Остров Рождества", - "CY": "Кипр", - "CZ": "Чехия", - "DE": "Германия", - "DJ": "Джибути", - "DK": "Дания", - "DM": "Доминика", - "DO": "Доминиканская Республика", - "DZ": "Алжир", - "EC": "Эквадор", - "EE": "Эстония", - "EG": "Египет", - "EH": "Западная Сахара", - "ER": "Эритрея", - "ES": "Испания", - "ET": "Эфиопия", - "FI": "Финляндия", - "FJ": "Фиджи", - "FK": "Фолклендские острова", - "FM": "Микронезия", - "FO": "Фарерские о-ва", - "FR": "Франция", - "GA": "Габон", - "GB": "Великобритания", - "GD": "Гренада", - "GE": "Грузия", - "GF": "Французская Гвиана", - "GG": "Гернси", - "GH": "Гана", - "GI": "Гибралтар", - "GL": "Гренландия", - "GM": "Гамбия", - "GN": "Гвинея", - "GP": "Гваделупа", - "GQ": "Экваториальная Гвинея", - "GR": "Греция", - "GS": "Южная Георгия и Южные Сандвичевы острова", - "GT": "Гватемала", - "GU": "Гуам", - "GW": "Гвинея-Бисау", - "GY": "Гайана", - "HK": "Гонконг", - "HM": "Остров Херд и остров Макдональд", - "HN": "Гондурас", - "HR": "Хорватия", - "HT": "Гаити", - "HU": "Венгрия", - "ID": "Индонезия", - "IE": "Ирландия", - "IL": "Израиль", - "IM": "о-в Мэн", - "IN": "Индия", - "IO": "Британская территория в Индийском океане ", - "IQ": "Ирак", - "IR": "Иран", - "IS": "Исландия", - "IT": "Италия", - "JE": "Джерси", - "JM": "Ямайка", - "JO": "Иордания", - "JP": "Япония", - "KE": "Кения", - "KG": "Киргизия", - "KH": "Камбоджия", - "KI": "Кирибати", - "KM": "Коморы", - "KN": "Сент-Китс и Невис", - "KP": "Северная Корея", - "KR": "Южная Корея", - "KW": "Кувейт", - "KY": "Каймановы острова", - "KZ": "Казахстан", - "LA": "Лаос", - "LB": "Ливан", - "LC": "Сент-Люсия", - "LI": "Лихтенштейн", - "LK": "Шри-Ланка", - "LR": "Либерия", - "LS": "Лесото", - "LT": "Литва", - "LU": "Люксембург", - "LV": "Латвия", - "LY": "Ливия", - "MA": "Морокко", - "MC": "Монако", - "MD": "Молдавия", - "ME": "Черногория", - "MF": "Сен-Мартен (владение Франции)", - "MG": "Мадагарскар", - "MH": "Маршалловы о-ва", - "MK": "Северная Македония", - "ML": "Мали", - "MM": "Мьянма (Бирма)", - "MN": "Монголия", - "MO": "Макао", - "MP": "Северные Марианские о-ва", - "MQ": "Мартиника", - "MR": "Мавритания", - "MS": "Монтсеррат", - "MT": "Мальта", - "MU": "о. Маврикий", - "MV": "Мальдивы", - "MW": "Малави", - "MX": "Мексика", - "MY": "Малазия", - "MZ": "Мозамбик", - "NA": "Намибия", - "NC": "Новая Каледония", - "NE": "Нигер", - "NF": "Остров Норфолк", - "NG": "Нигерия", - "NI": "Никарагуа", - "NL": "Нидерланды", - "NO": "Норвегия", - "NP": "Непал", - "NR": "Науру", - "NU": "Ниуэ", - "NZ": "Новая Зеландия", - "OM": "Оман", - "PA": "Панама", - "PE": "Перу", - "PF": "Французская Полинезия", - "PG": "Папуа-Новая Гвинея", - "PH": "Филиппины", - "PK": "Пакистан", - "PL": "Польша", - "PM": "Сен-Пьер и Микелон", - "PN": "о-ва Питкэрн", - "PR": "Пуэрто-Рико", - "PS": "Палестина", - "PT": "Португалия", - "PW": "Палау", - "PY": "Парагва", - "QA": "Катар", - "RE": "Реюньон", - "RO": "Румыния", - "RS": "Сербия", - "RU": "Российская Федерация", - "RW": "Руанда", - "SA": "Саудовская Аравия", - "SB": "Соломоновы о-ва", - "SC": "Сейшелы", - "SD": "Судан", - "SE": "Швеция", - "SG": "Сингапур", - "SH": "о. Св. Елены", - "SI": "Словения", - "SJ": "Шпицберген и Ян-Майен", - "SK": "Словакия", - "SL": "Сьерра-Леоне", - "SM": "Сан-Марино", - "SN": "Сенегал", - "SO": "Сомали", - "SR": "Суринам", - "SS": "Южный Судан", - "ST": "Сан-Томе и Принсипи", - "SV": "Сальвадор", - "SX": "Синт-Мартен (Дания)", - "SY": "Сирия", - "SZ": "Эсватини", - "TC": "Острова Теркс и Кайкос", - "TD": "Чад", - "TF": "Французские Южные и Антарктические территории", - "TG": "Того", - "TH": "Тайланд", - "TJ": "Таджикистан", - "TK": "Токелау", - "TL": "Восточный Тимор", - "TM": "Туркмения", - "TN": "Тунис", - "TO": "Тонго", - "TR": "Турция", - "TT": "Тринидад и Тобаго", - "TV": "Тувалу", - "TW": "Тайвань", - "TZ": "Танзания", - "UA": "Украина", - "UG": "Уганда", - "UM": "Внешние малые о-ва (США)", - "US": "США", - "UY": "Уругвай", - "UZ": "Узбекистан", - "VA": "Святой Престол", - "VC": "Сент-Винсент и Гренадины", - "VE": "Венесуэла", - "VG": "Британские Виргинские острова", - "VI": "Американские Виргинские острова", - "VN": "Вьетнам", - "VU": "Вануату", - "WF": "о-ва Уоллис и Футуна", - "WS": "Самоа", - "YE": "Йемен", - "YT": "Майотта", - "XK": "Косово", - "ZA": "ЮАР", - "ZM": "Замбия", - "ZW": "Зимбабве", - "EU": "Европейский Союз" - }, - "languages": { - "en-US": "Английский - США", - "fr": "Французский", - "nl": "Датский", - "pt_BR": "Португальский - Бразилия" - } - }, - "KnownHosts": { - "label": "&Хост", - "add": "Добавить новый сервер", - "toast": "Сервер успешно {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "ЗНАЕТЕ ЛИ ВЫ", - "subtitle": "<1>Cockatrice поддерживается энтузиастами<1>которые любят карточные игры!" - }, - "LoginContainer": { - "header": { - "title": "Логин", - "subtitle": "Кросс-платформенный виртуальный стол для мультиплеерных ККИ" - }, - "footer": { - "registerPrompt": "Ещё не зарегистрированы?", - "registerAction": "Создать учетную запись", - "credit": "Cockatrice - проект с открытым исходным кодом", - "version": "Версия" - }, - "content": { - "subtitle1": "Играйте в мультиплеерные ККИ онлайн", - "subtitle2": "Кросс-платформенный виртуальный стол для мультиплеерных ККИ. Всегда будет бесплатным." - }, - "toasts": { - "passwordResetSuccessToast": "Пароль сброшен успешно", - "accountActivationSuccess": "Учетная запись активирована успешно" - } - }, - "UnsupportedContainer": { - "title": "Браузер не поддерживается", - "subtitle1": "Обновите бразуер и/или проверьте права доступа", - "subtitle2": "Обратите внимание, что использование анонимных браузеров может привести к неработоспособности некоторых фич и проблемами с правами доступа" - }, - "AccountActivationDialog": { - "title": "Активация учетной записи", - "subtitle1": "Ваша учетная запись не активирована", - "subtitle2": "Ваш аккаунт пока не активирован. Вам необходимо следовать указаниям по активации, отправленным на ваш email" - }, - "KnownHostDialog": { - "title": "{модифицировать, выбрать, редактировать {Edit} другой {Add}} известный хост", - "subtitle": "Добавление нового хоста позволит вам присоединяться к различным серверам. Введите данные о сервере в ваш список хостов" - }, - "RegistrationDialog": { - "title": "Создать новый аккаунт" - }, - "RequestPasswordResetDialog": { - "title": "Запросить сброс пароля" - }, - "ResetPasswordDialog": { - "title": "Сбросить пароль" - }, - "AccountActivationForm": { - "error": { - "failed": "Не удалось активировать аккаунт" - }, - "label": { - "activate": "Активировать аккаунт" - } - }, - "KnownHostForm": { - "help": "Нужна помощь в добавлении нового хоста?", - "label": { - "add": "Добавить хост", - "find": "Найти хост" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Автоматическое подключение", - "forgot": "Забыли пароль", - "login": "Логин", - "savePassword": "Сохранить пароль", - "savedPassword": "Сохранённый пароль" - } - }, - "RegisterForm": { - "label": { - "register": "Зарегистрироваться" - }, - "toast": { - "registerSuccess": "Регистрация прошла успешно!" - } - }, - "RequestPasswordResetForm": { - "error": "Не удалось запросить сброс пароля", - "mfaEnabled": "На сервере включена многофакторная аутентификация", - "request": "Запросить токен сброса", - "skipRequest": "У меня уже есть токен сброса" - }, - "ResetPasswordForm": { - "error": "Не удалось сбросить пароль", - "label": { - "reset": "Сбросить пароля" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/tok/translation.json b/webclient/public/locales/tok/translation.json deleted file mode 100644 index 46437f558..000000000 --- a/webclient/public/locales/tok/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "Toki Pona", - "disconnect": "mi o weka tan kulupu", - "label": { - "confirmPassword": "nimi ken li pona ala pona", - "confirmSure": "ni li pona ala pona", - "country": "ma", - "delete": "mi o weka e ni", - "email": "nimi Email", - "hostName": "nimi pi ilo lawa", - "hostAddress": "ma pi ilo lawa", - "password": "nimi ken pi kama sina", - "passwordAgain": "o pana sin e nimi ken pi kama sina", - "port": "nanpa pi ilo lawa", - "realName": "nimi sina lon", - "saveChanges": "mi o awen e ante sina", - "token": "ijo lili", - "username": "nimi sina" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "nimi ken nanpa wan li sama ala nimi ken nanpa tu", - "required": "o ni" - }, - "countries": { - "AD": "ma Antola", - "AE": "United Arab Emirates", - "AF": "ma Akanisan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "ma Sipe", - "AM": "ma Aja", - "AO": "ma Ankola", - "AQ": "ma Antasika", - "AR": "ma Alensina", - "AS": "American Samoa", - "AT": "ma Esalasi", - "AU": "ma Oselija", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "ma Papeto", - "BD": "ma Panla", - "BE": "ma Pesije", - "BF": "ma Pukinapaso", - "BG": "ma Pokasi", - "BH": "ma Palani", - "BI": "Burundi", - "BJ": "ma Penen", - "BL": "Saint Barthélemy", - "BM": "ma Pemuta", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "ma Pasila", - "BS": "ma Pawama", - "BT": "ma Putan", - "BV": "Bouvet Island", - "BW": "ma Posuwana", - "BY": "ma Pelalusi", - "BZ": "Belize", - "CA": "ma Kanata", - "CC": "Cocos (Keeling) Islands", - "CD": "ma DR Konko", - "CF": "ma Santapiken", - "CG": "ma Konko", - "CH": "ma Suwasi", - "CI": "ma Kowisa", - "CK": "Cook Islands", - "CL": "ma Sile", - "CM": "ma Kamelun", - "CN": "ma Sonko", - "CO": "Colombia", - "CR": "ma Kosalika", - "CU": "ma Kupa", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "ma Kiposi", - "CZ": "ma Seki", - "DE": "ma Tosi", - "DJ": "ma Sipusi", - "DK": "ma Tansi", - "DM": "ma Watukupuli", - "DO": "ma Tominika", - "DZ": "ma Sasali", - "EC": "ma Ekato", - "EE": "ma Esi", - "EG": "ma Masu", - "EH": "Western Sahara", - "ER": "ma Eliteja", - "ES": "ma Epanja", - "ET": "ma Isijopija", - "FI": "ma Sumi", - "FJ": "ma Pisi", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "ma Kanse", - "GA": "ma Kapon", - "GB": "ma Juke", - "GD": "ma Kenata", - "GE": "ma Katelo", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "ma Kana", - "GI": "Gibraltar", - "GL": "ma Kalalinuna", - "GM": "ma Kanpija", - "GN": "ma Kine", - "GP": "Guadeloupe", - "GQ": "ma Kinejekatolija", - "GR": "ma Elena", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "ma Katemala", - "GU": "Guam", - "GW": "ma Kinepisa", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "ma Ontula", - "HR": "ma Lowasi", - "HT": "ma Awisi", - "HU": "ma Mosijo", - "ID": "ma Intonesija", - "IE": "ma Alan", - "IL": "ma Isale", - "IM": "Isle of Man", - "IN": "ma Palata", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "ma Isilan", - "IT": "ma Italija", - "JE": "Jersey", - "JM": "ma Sameka", - "JO": "ma Utun", - "JP": "ma Nijon", - "KE": "ma Kenja", - "KG": "Kyrgyzstan", - "KH": "ma Kanpusi", - "KI": "ma Kilipasi", - "KM": "ma Komo", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "ma Anku", - "KW": "ma Kuwasi", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "ma Lunpan", - "LC": "Saint Lucia", - "LI": "ma Lisensan", - "LK": "ma Lanka", - "LR": "ma Lapewija", - "LS": "ma Lesoto", - "LT": "ma Lijatuwa", - "LU": "ma Lusepu", - "LV": "ma Lawi", - "LY": "ma Lipija", - "MA": "ma Malipe", - "MC": "Monaco", - "MD": "ma Motowa", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "ma Maketonija", - "ML": "ma Mali", - "MM": "ma Mijama", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "ma Mulitanija", - "MS": "Montserrat", - "MT": "Malta", - "MU": "ma Mowisi", - "MV": "Maldives", - "MW": "Malawi", - "MX": "ma Mesiko", - "MY": "ma Malasija", - "MZ": "ma Mosanpi", - "NA": "ma Namipija", - "NC": "New Caledonia", - "NE": "ma Nise", - "NF": "Norfolk Island", - "NG": "ma Naselija", - "NI": "Nicaragua", - "NL": "ma Netelan", - "NO": "ma Nosiki", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "ma Nusilan", - "OM": "ma Uman", - "PA": "ma Panama", - "PE": "ma Pelu", - "PF": "French Polynesia", - "PG": "ma Papuwanijukini", - "PH": "ma Pilipina", - "PK": "ma Pakisan", - "PL": " ma Posuka", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "ma Pilisin", - "PT": "ma Potuke", - "PW": "Palau", - "PY": "ma Palakawi", - "QA": "Qatar", - "RE": "Réunion", - "RO": "ma Lomani", - "RS": "ma Sopisi", - "RU": "ma Losi", - "RW": "ma Luwanta", - "SA": "ma Sawusi", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "ma Sutan", - "SE": "ma Wensa", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "ma Lowensina", - "SJ": "Svalbard and Jan Mayen", - "SK": "ma Lowenki", - "SL": "ma Sijelalijon", - "SM": "ma Samalino", - "SN": "ma Seneka", - "SO": "ma Somalija", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "ma Sulija", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "ma Sate", - "TF": "TAAF", - "TG": "ma Toko", - "TH": "ma Tawi", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "ma Tunisi", - "TO": " ma Tona", - "TR": "ma Tuki", - "TT": "ma Sinita", - "TV": "ma Tuwalu", - "TW": "ma Tawan", - "TZ": "ma Tansanija", - "UA": "ma Ukawina", - "UG": "ma Ukanta", - "UM": "United States Minor Outlying Islands", - "US": "ma Mewika", - "UY": "ma Ulukawi", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "ma SVG", - "VE": "ma Penesuwela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "ma Wije", - "VU": "ma Wanuwatu", - "WF": "Wallis and Futuna", - "WS": "ma Samowa", - "YE": "ma Jamanija", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "ma Setapika", - "ZM": "ma Sanpija", - "ZW": "ma Sinpapuwe", - "EU": "ma kulupu Elopa" - }, - "languages": { - "en-US": "toki Inli", - "fr": "toki Kanse", - "nl": "toki Netelan", - "pt_BR": "toki Potuke pi ma Pasila" - } - }, - "KnownHosts": { - "label": "ilo lawa", - "add": "mi o ken e ilo lawa sin", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "sina sona ala sona e ni:", - "subtitle": "<1>jan li pali e ilo Cockatrice lon wile taso! <1>musi lipu li pona tawa ona!" - }, - "LoginContainer": { - "header": { - "title": "mi o kama e sina lon kulupu", - "subtitle": "ilo Cockatrice li ken e musi lipu ale. nasin mute ilo li ken lon ona." - }, - "footer": { - "registerPrompt": "sina jo ala jo e sijelo kulupu?", - "registerAction": "mi o pali e sijelo kulupu sina", - "credit": "insa pi ilo Cockatrice li len ala", - "version": "nanpa ante" - }, - "content": { - "subtitle1": "Play multiplayer card games online.", - "subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free." - }, - "toasts": { - "passwordResetSuccessToast": "Password Reset Successfully", - "accountActivationSuccess": "Account Activated Successfully" - } - }, - "UnsupportedContainer": { - "title": "Unsupported Browser", - "subtitle1": "Please update your browser and/or check your permissions.", - "subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features." - }, - "AccountActivationDialog": { - "title": "Account Activation", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list." - }, - "RegistrationDialog": { - "title": "Create New Account" - }, - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - }, - "ResetPasswordDialog": { - "title": "mi o sin e nimi ken" - }, - "AccountActivationForm": { - "error": { - "failed": "Account activation failed" - }, - "label": { - "activate": "Activate Account" - } - }, - "KnownHostForm": { - "help": "Need help adding a new host?", - "label": { - "add": "Add Host", - "find": "Find Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "nimi ken pi kama sina li weka tan sona", - "login": "mi o kama e sina lon kulupu", - "savePassword": "mi o awen e nimi ken", - "savedPassword": "mi awen e nimi ken" - } - }, - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - }, - "RequestPasswordResetForm": { - "error": "wile ante pi nimi ken li pakala", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - }, - "ResetPasswordForm": { - "error": "ante pi nimi ken li pakala", - "label": { - "reset": "mi o sin e nimi ken" - } - } -} \ No newline at end of file diff --git a/webclient/public/locales/yue/translation.json b/webclient/public/locales/yue/translation.json deleted file mode 100644 index b14041496..000000000 --- a/webclient/public/locales/yue/translation.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "", - "disconnect": "斷開連線", - "label": { - "confirmPassword": "確認新密碼:", - "confirmSure": "你確定嗎?", - "country": "國家:", - "delete": "删除", - "email": "電郵:", - "hostName": "主機名稱", - "hostAddress": "主機地址", - "password": "密碼:", - "passwordAgain": "再次輸入密碼:", - "port": "端口:", - "realName": "實名:", - "saveChanges": "儲存變更", - "token": "令牌", - "username": "用戶名稱:" - }, - "validation": { - "minChars": "最少需要{數目} {數目,眾數,單{字元}或其他{眾字元}}", - "passwordsMustMatch": "新舊密碼不一致.", - "required": "必須" - }, - "countries": { - "AD": "安道爾", - "AE": "阿拉伯聯合大公國", - "AF": "阿富汗", - "AG": "安地卡及巴布達", - "AI": "安圭拉", - "AL": "阿爾巴尼亞", - "AM": "亞美尼亞", - "AO": "安哥拉", - "AQ": "南極洲", - "AR": "阿根廷", - "AS": "美屬薩摩亞", - "AT": "奧地利", - "AU": "澳洲", - "AW": "阿魯巴", - "AX": "奧蘭群島", - "AZ": "亞塞拜然", - "BA": "波士尼亞與赫塞哥維納", - "BB": "巴貝多", - "BD": "孟加拉", - "BE": "比利時", - "BF": "布吉納法索", - "BG": "保加利亞", - "BH": "巴林", - "BI": "蒲隆地", - "BJ": "貝南", - "BL": "聖巴泰勒米", - "BM": "百慕達", - "BN": "汶萊達魯薩蘭國", - "BO": "玻利維亞", - "BQ": "博內爾島、聖尤斯特歇斯島和薩巴島", - "BR": "巴西", - "BS": "巴哈馬", - "BT": "不丹", - "BV": "布韋島", - "BW": "波札那", - "BY": "白俄羅斯", - "BZ": "貝里斯", - "CA": "加拿大", - "CC": "科科斯(基林)群島", - "CD": "剛果民主共和國", - "CF": "中非共和國", - "CG": "剛果共和國", - "CH": "瑞士", - "CI": "科特迪瓦", - "CK": "庫克群島", - "CL": "智利", - "CM": "喀麥隆", - "CN": "中國", - "CO": "哥倫比亞", - "CR": "哥斯大黎加", - "CU": "古巴", - "CV": "維德角", - "CW": "庫拉索", - "CX": "聖誕島", - "CY": "賽普勒斯", - "CZ": "捷克", - "DE": "德國", - "DJ": "吉布地", - "DK": "丹麥", - "DM": "多明尼加", - "DO": "多明尼加共和國", - "DZ": "阿爾及利亞", - "EC": "厄瓜多", - "EE": "愛沙尼亞", - "EG": "埃及", - "EH": "西撒哈拉", - "ER": "厄利垂亞", - "ES": "西班牙", - "ET": "衣索比亞", - "FI": "芬蘭", - "FJ": "斐濟", - "FK": "福克蘭群島", - "FM": "密克羅尼西亞", - "FO": "法羅群島", - "FR": "法國", - "GA": "加彭", - "GB": "英國", - "GD": "格瑞那達", - "GE": "喬治亞", - "GF": "法屬圭亞那", - "GG": "根西島", - "GH": "迦納", - "GI": "直布羅陀", - "GL": "格陵蘭", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Add new host", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "DID YOU KNOW", - "subtitle": "<1>Cockatrice is run by volunteers<1>that love card games!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "A cross-platform virtual tabletop for multiplayer card games." - }, - "footer": { - "registerPrompt": "Not registered yet?", - "registerAction": "Create an account", - "credit": "Cockatrice is an open source project", - "version": "Version" - }, - "content": { - "subtitle1": "Play multiplayer card games online.", - "subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free." - }, - "toasts": { - "passwordResetSuccessToast": "Password Reset Successfully", - "accountActivationSuccess": "Account Activated Successfully" - } - }, - "UnsupportedContainer": { - "title": "Unsupported Browser", - "subtitle1": "Please update your browser and/or check your permissions.", - "subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features." - }, - "AccountActivationDialog": { - "title": "Account Activation", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list." - }, - "RegistrationDialog": { - "title": "Create New Account" - }, - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - }, - "ResetPasswordDialog": { - "title": "Reset Password" - }, - "AccountActivationForm": { - "error": { - "failed": "Account activation failed" - }, - "label": { - "activate": "Activate Account" - } - }, - "KnownHostForm": { - "help": "Need help adding a new host?", - "label": { - "add": "Add Host", - "find": "Find Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "Forgot Password", - "login": "Login", - "savePassword": "Save Password", - "savedPassword": "Saved Password" - } - }, - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - }, - "RequestPasswordResetForm": { - "error": "Request password reset failed", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - }, - "ResetPasswordForm": { - "error": "Password reset failed", - "label": { - "reset": "Reset Password" - } - } -} \ No newline at end of file diff --git a/webclient/public/logo192.png b/webclient/public/logo192.png deleted file mode 100644 index fa313abf5..000000000 Binary files a/webclient/public/logo192.png and /dev/null differ diff --git a/webclient/public/logo512.png b/webclient/public/logo512.png deleted file mode 100644 index bd5d4b5e2..000000000 Binary files a/webclient/public/logo512.png and /dev/null differ diff --git a/webclient/public/manifest.json b/webclient/public/manifest.json deleted file mode 100644 index 080d6c77a..000000000 --- a/webclient/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/webclient/public/pb/.gitignore b/webclient/public/pb/.gitignore deleted file mode 100644 index 571442132..000000000 --- a/webclient/public/pb/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore all files -* -# Except gitignore -!.gitignore \ No newline at end of file diff --git a/webclient/public/reset.css b/webclient/public/reset.css deleted file mode 100644 index af944401f..000000000 --- a/webclient/public/reset.css +++ /dev/null @@ -1,48 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} \ No newline at end of file diff --git a/webclient/public/robots.txt b/webclient/public/robots.txt deleted file mode 100644 index 01b0f9a10..000000000 --- a/webclient/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/webclient/src/api/AdminService.tsx b/webclient/src/api/AdminService.tsx deleted file mode 100644 index 623ad546a..000000000 --- a/webclient/src/api/AdminService.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { AdminCommands } from 'websocket'; - -export class AdminService { - static adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - AdminCommands.adjustMod(userName, shouldBeMod, shouldBeJudge); - } - - static reloadConfig(): void { - AdminCommands.reloadConfig(); - } - - static shutdownServer(reason: string, minutes: number): void { - AdminCommands.shutdownServer(reason, minutes); - } - - static updateServerMessage(): void { - AdminCommands.updateServerMessage(); - } -} diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx deleted file mode 100644 index 7b3a46988..000000000 --- a/webclient/src/api/AuthenticationService.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { StatusEnum, User, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; -import { SessionCommands, webClient } from 'websocket'; -import { ProtoController } from 'websocket/services/ProtoController'; - -export class AuthenticationService { - static login(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.LOGIN); - } - - static testConnection(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.TEST_CONNECTION); - } - - static register(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.REGISTER); - } - - static activateAccount(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.ACTIVATE_ACCOUNT); - } - - static resetPasswordRequest(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_REQUEST); - } - - static resetPasswordChallenge(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); - } - - static resetPassword(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET); - } - - static disconnect(): void { - SessionCommands.disconnect(); - } - - static isConnected(state: number): boolean { - return state === StatusEnum.LOGGED_IN; - } - - static isModerator(user: User): boolean { - const moderatorLevel = ProtoController.root.ServerInfo_User.UserLevelFlag.IsModerator; - // @TODO tell cockatrice not to do this so shittily - return (user.userLevel & moderatorLevel) === moderatorLevel; - } - - static isAdmin() { - - } - - static connectionAttemptMade() { - return webClient.connectionAttemptMade; - } -} diff --git a/webclient/src/api/ModeratorService.tsx b/webclient/src/api/ModeratorService.tsx deleted file mode 100644 index 6c22ee55e..000000000 --- a/webclient/src/api/ModeratorService.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ModeratorCommands } from 'websocket'; -import { LogFilters } from 'types'; - -export class ModeratorService { - static banFromServer(minutes: number, userName?: string, address?: string, reason?: string, - visibleReason?: string, clientid?: string, removeMessages?: number): void { - ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages); - } - - static getBanHistory(userName: string): void { - ModeratorCommands.getBanHistory(userName); - } - - static getWarnHistory(userName: string): void { - ModeratorCommands.getWarnHistory(userName); - } - - static getWarnList(modName: string, userName: string, userClientid: string): void { - ModeratorCommands.getWarnList(modName, userName, userClientid); - } - - static viewLogHistory(filters: LogFilters): void { - ModeratorCommands.viewLogHistory(filters); - } - - static warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { - ModeratorCommands.warnUser(userName, reason, clientid, removeMessages); - } -} diff --git a/webclient/src/api/RoomsService.tsx b/webclient/src/api/RoomsService.tsx deleted file mode 100644 index bfb63d92a..000000000 --- a/webclient/src/api/RoomsService.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { RoomCommands, SessionCommands } from 'websocket'; - -export class RoomsService { - static joinRoom(roomId: number): void { - SessionCommands.joinRoom(roomId); - } - - static leaveRoom(roomId: number): void { - RoomCommands.leaveRoom(roomId); - } - - static roomSay(roomId: number, message: string): void { - RoomCommands.roomSay(roomId, message); - } -} diff --git a/webclient/src/api/SessionService.tsx b/webclient/src/api/SessionService.tsx deleted file mode 100644 index 2787f098d..000000000 --- a/webclient/src/api/SessionService.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { SessionCommands } from 'websocket'; - -export class SessionService { - static addToBuddyList(userName: string) { - SessionCommands.addToBuddyList(userName); - } - - static removeFromBuddyList(userName: string) { - SessionCommands.removeFromBuddyList(userName); - } - - static addToIgnoreList(userName: string) { - SessionCommands.addToIgnoreList(userName); - } - - static removeFromIgnoreList(userName: string) { - SessionCommands.removeFromIgnoreList(userName); - } - - static changeAccountPassword(oldPassword: string, newPassword: string, hashedNewPassword?: string): void { - SessionCommands.accountPassword(oldPassword, newPassword, hashedNewPassword); - } - - static changeAccountDetails(passwordCheck: string, realName?: string, email?: string, country?: string): void { - SessionCommands.accountEdit(passwordCheck, realName, email, country); - } - - static changeAccountImage(image: Uint8Array): void { - SessionCommands.accountImage(image); - } - - static sendDirectMessage(userName: string, message: string): void { - SessionCommands.message(userName, message); - } - - static getUserInfo(userName: string): void { - SessionCommands.getUserInfo(userName); - } - - static getUserGames(userName: string): void { - SessionCommands.getGamesOfUser(userName); - } -} diff --git a/webclient/src/api/index.ts b/webclient/src/api/index.ts deleted file mode 100644 index c4f67092e..000000000 --- a/webclient/src/api/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { AdminService } from './AdminService'; -export { AuthenticationService } from './AuthenticationService'; -export { ModeratorService } from './ModeratorService'; -export { RoomsService } from './RoomsService'; -export { SessionService } from './SessionService'; diff --git a/webclient/src/common.i18n.json b/webclient/src/common.i18n.json deleted file mode 100644 index 64a102c38..000000000 --- a/webclient/src/common.i18n.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "Common": { - "language": "English", - "disconnect": "Disconnect", - "label": { - "confirmPassword": "Confirm Password", - "confirmSure": "Are you sure?", - "country": "Country", - "delete": "Delete", - "email": "Email", - "hostName": "Host Name", - "hostAddress": "Host Address", - "password": "Password", - "passwordAgain": "Password Again", - "port": "Port", - "realName": "Real Name", - "saveChanges": "Save Changes", - "token": "Token", - "username": "Username" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "Passwords don't match", - "required": "Required" - }, - "countries": { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos (Keeling) Islands", - "CD": "DR Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - } -} diff --git a/webclient/src/components/Card/Card.css b/webclient/src/components/Card/Card.css deleted file mode 100644 index 976618634..000000000 --- a/webclient/src/components/Card/Card.css +++ /dev/null @@ -1,4 +0,0 @@ -.card { - width: 100%; - height: 100%; -} diff --git a/webclient/src/components/Card/Card.tsx b/webclient/src/components/Card/Card.tsx deleted file mode 100644 index f89622c3b..000000000 --- a/webclient/src/components/Card/Card.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line -import React, { useMemo, useState } from 'react'; - -import { CardDTO } from 'services'; - -import './Card.css'; - -interface CardProps { - card: CardDTO; -} - -const Card = ({ card }: CardProps) => { - const src = `https://api.scryfall.com/cards/${card?.identifiers?.scryfallId}?format=image`; - - return card && ( - {card?.name} - ); -} - -export default Card; diff --git a/webclient/src/components/CardDetails/CardDetails.css b/webclient/src/components/CardDetails/CardDetails.css deleted file mode 100644 index 7009049d2..000000000 --- a/webclient/src/components/CardDetails/CardDetails.css +++ /dev/null @@ -1,45 +0,0 @@ -.cardDetails { - padding: 10px; - width: calc(400px * .716); - font-size: 10px; -} - -.cardDetails-card { - height: 400px; - margin: 0 auto; -} - -.cardDetails-attribute { - display: flex; - justify-content: space-between; - align-items: baseline; -} - -.cardDetails-attributes { - margin: 10px 0; -} - -.cardDetails-attribute__label { - text-transform: uppercase; - font-size: 10px; - margin-right: 10px; -} - -.cardDetails-attribute__value { - text-align: right; -} - -.cardDetails-text { - font-size: 12px; - padding: 5px; - background: rgba(0, 0, 0, .15); - white-space: pre-line; -} - -.cardDetails-text__flavor { - font-style: italic; -} - -.cardDetails-text__current:not(:empty) + .cardDetails-text__flavor { - margin-top: 10px; -} diff --git a/webclient/src/components/CardDetails/CardDetails.tsx b/webclient/src/components/CardDetails/CardDetails.tsx deleted file mode 100644 index 84196fda6..000000000 --- a/webclient/src/components/CardDetails/CardDetails.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// eslint-disable-next-line -import React, { useMemo, useState } from 'react'; - -import { CardDTO } from 'services'; - -import Card from '../Card/Card'; - -import './CardDetails.css'; - -interface CardProps { - card: CardDTO; -} - -// @TODO: add missing fields (loyalty, hand, etc) - -const CardDetails = ({ card }: CardProps) => { - return ( -
-
- -
- - { - card && ( -
-
-
- Name: - {card.name} -
- - { - (!card.power && !card.toughness) ? null : ( -
- P/T: - {card.power || 0}/{card.toughness || 0} -
- ) - } - - { - !card.manaCost ? null : ( -
- Cost: - {card.manaCost.replace(/\{|\}/g, '')} -
- ) - } - - { - !card.convertedManaCost ? null : ( -
- CMC: - {card.convertedManaCost} -
- ) - } - - { - !card.colorIdentity?.length ? null : ( -
- Identity: - {card.colorIdentity.join('')} -
- ) - } - - { - !card.colors?.length ? null : ( -
- Color(s): - {card.colors.join('')} -
- ) - } - - { - !card.types?.length ? null : ( -
- Main Type: - {card.types.join(', ')} -
- ) - } - - { - !card.type ? null : ( -
- Type: - {card.type} -
- ) - } - - { - !card.side ? null : ( -
- Side: - {card.side} -
- ) - } - - { - !card.layout ? null : ( -
- Layout: - {card.layout} -
- ) - } -
- -
-
- {card.text?.trim()} -
- -
- {card.flavorText?.trim()} -
-
-
- ) - } -
- ); -} - -export default CardDetails; diff --git a/webclient/src/components/CheckboxField/CheckboxField.css b/webclient/src/components/CheckboxField/CheckboxField.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/webclient/src/components/CheckboxField/CheckboxField.tsx b/webclient/src/components/CheckboxField/CheckboxField.tsx deleted file mode 100644 index 562687489..000000000 --- a/webclient/src/components/CheckboxField/CheckboxField.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from '@mui/material/FormControlLabel'; - -const CheckboxField = (props) => { - const { input: { value, onChange }, label, ...args } = props; - - // @TODO this isnt unchecking properly - return ( - onChange(checked)} - color="primary" - /> - } - /> - ); -}; - -export default CheckboxField; diff --git a/webclient/src/components/CountryDropdown/CountryDropdown.css b/webclient/src/components/CountryDropdown/CountryDropdown.css deleted file mode 100644 index 9b1679516..000000000 --- a/webclient/src/components/CountryDropdown/CountryDropdown.css +++ /dev/null @@ -1,13 +0,0 @@ -.CountryDropdown { - width: 100%; -} - -.CountryDropdown-item { - display: flex; - align-items: center; -} - -.CountryDropdown-item__image { - width: 1.5em; - margin-right: 1em; -} diff --git a/webclient/src/components/CountryDropdown/CountryDropdown.tsx b/webclient/src/components/CountryDropdown/CountryDropdown.tsx deleted file mode 100644 index 09b1cf71e..000000000 --- a/webclient/src/components/CountryDropdown/CountryDropdown.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Select, MenuItem } from '@mui/material'; -import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; -import { useTranslation } from 'react-i18next'; - -import { useLocaleSort } from 'hooks'; -import { Images } from 'images/Images'; -import { countryCodes } from 'types'; - - -import './CountryDropdown.css'; - -const CountryDropdown = ({ input: { onChange } }) => { - const [value, setValue] = useState(''); - const { t } = useTranslation(); - - useEffect(() => onChange(value), [value]); - - const translateCountry = country => t(`Common.countries.${country}`); - const sortedCountries = useLocaleSort(countryCodes, translateCountry); - - return ( - - Country - - - ) -}; - -export default CountryDropdown; diff --git a/webclient/src/components/Guard/AuthGuard.tsx b/webclient/src/components/Guard/AuthGuard.tsx deleted file mode 100644 index eea42dc7b..000000000 --- a/webclient/src/components/Guard/AuthGuard.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { Navigate } from 'react-router-dom'; - -import { ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; - -import { AuthenticationService } from 'api'; - -const AuthGuard = ({ state }: AuthGuardProps) => { - return !AuthenticationService.isConnected(state) - ? - :
; -}; - -interface AuthGuardProps { - state: number; -} - -const mapStateToProps = state => ({ - state: ServerSelectors.getState(state), -}); - -export default connect(mapStateToProps)(AuthGuard); diff --git a/webclient/src/components/Guard/ModGuard.tsx b/webclient/src/components/Guard/ModGuard.tsx deleted file mode 100644 index c8bc6d663..000000000 --- a/webclient/src/components/Guard/ModGuard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { Navigate } from 'react-router-dom'; - -import { ServerSelectors } from 'store'; -import { User } from 'types'; - -import { AuthenticationService } from 'api'; -import { RouteEnum } from 'types'; - -class ModGuard extends Component { - render() { - return !AuthenticationService.isModerator(this.props.user) - ? - : ''; - } -}; - -interface ModGuardProps { - user: User; -} - -const mapStateToProps = state => ({ - user: ServerSelectors.getUser(state), -}); - -export default connect(mapStateToProps)(ModGuard); diff --git a/webclient/src/components/InputAction/InputAction.css b/webclient/src/components/InputAction/InputAction.css deleted file mode 100644 index 25e784a3a..000000000 --- a/webclient/src/components/InputAction/InputAction.css +++ /dev/null @@ -1,19 +0,0 @@ -.input-action { - display: flex; - width: 100%; - align-items: center; -} - -.input-action, -.input-action__item, -.input-action__submit { - padding: 5px; -} - -.input-action__item { - width: 100%; - height: 100%; -} -.input-action__item > div { - margin: 0; -} \ No newline at end of file diff --git a/webclient/src/components/InputAction/InputAction.tsx b/webclient/src/components/InputAction/InputAction.tsx deleted file mode 100644 index 459b89904..000000000 --- a/webclient/src/components/InputAction/InputAction.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { Field } from 'react-final-form' -import Button from '@mui/material/Button'; - -import { InputField } from 'components'; - -import './InputAction.css'; - -const InputAction = ({ action, label, name, validate, disabled }) => ( -
-
- -
-
- -
-
-); - -InputAction.defaultProps = { - disabled: false, - validate: () => false, -} - -export default InputAction; diff --git a/webclient/src/components/InputField/InputField.css b/webclient/src/components/InputField/InputField.css deleted file mode 100644 index 819f7112a..000000000 --- a/webclient/src/components/InputField/InputField.css +++ /dev/null @@ -1,20 +0,0 @@ -.InputField { - position: relative; -} - -.InputField-validation { - position: absolute; - top: 0; - right: 0; - transform: translateY(-50%); - font-weight: bold; -} - -.InputField-error { - display: flex; - align-items: center; -} - -.InputField-error svg { - margin-left: 4px; -} diff --git a/webclient/src/components/InputField/InputField.tsx b/webclient/src/components/InputField/InputField.tsx deleted file mode 100644 index 3299ba383..000000000 --- a/webclient/src/components/InputField/InputField.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { styled } from '@mui/material/styles'; -import TextField from '@mui/material/TextField'; -import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined'; - -import './InputField.css'; - -const PREFIX = 'InputField'; - -const classes = { - root: `${PREFIX}-root` -}; - -const Root = styled('div')(({ theme }) => ({ - [`&.${classes.root}`]: { - '& .InputField-error': { - color: theme.palette.error.main - }, - - '& .InputField-warning': { - color: theme.palette.warning.main - }, - }, -})); - -const InputField = ({ input, meta, ...args }) => { - const { touched, error, warning } = meta; - - return ( - - { touched && ( -
- { - (error && -
- {error} - -
- ) || - - (warning &&
{warning}
) - } -
- ) } - - -
- ); -}; - -export default InputField; diff --git a/webclient/src/components/KnownHosts/KnownHosts.css b/webclient/src/components/KnownHosts/KnownHosts.css deleted file mode 100644 index 32a3a9fcc..000000000 --- a/webclient/src/components/KnownHosts/KnownHosts.css +++ /dev/null @@ -1,70 +0,0 @@ -.KnownHosts { -} - -.KnownHosts-form { - width: 100%; - position: relative; -} - -.KnownHosts-item { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -} - -.KnownHosts-item__wrapper { - display: flex; - align-items: center; -} - -.KnownHosts-item__label { - position: relative; -} - -.KnownHosts-item__label svg { - display: none; - position: absolute; - left: -.1em; - top: 50%; - transform: translate(-100%, -50%); - font-size: .9em; -} - -.KnownHosts-item__status { - display: none; -} - -.KnownHosts-item__status svg { - margin-left: -5px; - margin-right: 5px; -} - -.KnownHosts-validation { - position: absolute; - top: 0; - right: 0; - transform: translateY(-100%); - font-weight: bold; -} - -.KnownHosts-error { - display: flex; - align-items: center; -} - -.KnownHosts-error svg { - margin-left: 4px; -} - -.KnownHosts .MuiSelect-select .KnownHosts-item__status { - display: flex; -} - -.Mui-selected .KnownHosts-item__label svg { - display: block; -} - -.MuiSelect-select .KnownHosts-item__edit { - display: none; -} diff --git a/webclient/src/components/KnownHosts/KnownHosts.i18n.json b/webclient/src/components/KnownHosts/KnownHosts.i18n.json deleted file mode 100644 index 3de8fd887..000000000 --- a/webclient/src/components/KnownHosts/KnownHosts.i18n.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "KnownHosts": { - "label": "Host", - "add": "Add new host", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - } -} diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx deleted file mode 100644 index f65a98c38..000000000 --- a/webclient/src/components/KnownHosts/KnownHosts.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { styled } from '@mui/material/styles'; -import { useTranslation } from 'react-i18next'; -import { Select, MenuItem } from '@mui/material'; -import Button from '@mui/material/Button'; -import FormControl from '@mui/material/FormControl'; -import IconButton from '@mui/material/IconButton'; -import WifiTetheringIcon from '@mui/icons-material/WifiTethering'; -import PortableWifiOffIcon from '@mui/icons-material/PortableWifiOff'; -import InputLabel from '@mui/material/InputLabel'; -import Check from '@mui/icons-material/Check'; -import AddIcon from '@mui/icons-material/Add'; -import EditRoundedIcon from '@mui/icons-material/Edit'; -import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined'; - -import { AuthenticationService } from 'api'; -import { KnownHostDialog } from 'dialogs'; -import { useReduxEffect } from 'hooks'; -import { HostDTO } from 'services'; -import { ServerTypes } from 'store'; -import { DefaultHosts, Host, getHostPort } from 'types'; -import Toast from 'components/Toast/Toast'; - -import './KnownHosts.css'; - -enum TestConnection { - TESTING = 'testing', - FAILED = 'failed', - SUCCESS = 'success', -} - -const PREFIX = 'KnownHosts'; - -const classes = { - root: `${PREFIX}-root` -}; - -const Root = styled('div')(({ theme }) => ({ - [`&.${classes.root}`]: { - '& .KnownHosts-error': { - color: theme.palette.error.main - }, - - '& .KnownHosts-warning': { - color: theme.palette.warning.main - }, - - '& .KnownHosts-item': { - [`& .${TestConnection.TESTING}`]: { - color: theme.palette.warning.main - }, - [`& .${TestConnection.FAILED}`]: { - color: theme.palette.error.main - }, - [`& .${TestConnection.SUCCESS}`]: { - color: theme.palette.success.main - } - } - } -})); - -const KnownHosts = (props) => { - const { input: { onChange }, meta, disabled } = props; - const { touched, error, warning } = meta; - - const { t } = useTranslation(); - - const [hostsState, setHostsState] = useState({ - hosts: [], - selectedHost: {} as any, - }); - - const [dialogState, setDialogState] = useState({ - open: false, - edit: null, - }); - - const [testingConnection, setTestingConnection] = useState(null); - - const [showCreateToast, setShowCreateToast] = useState(false); - const [showDeleteToast, setShowDeleteToast] = useState(false); - const [showEditToast, setShowEditToast] = useState(false); - - const loadKnownHosts = useCallback(async () => { - const hosts = await HostDTO.getAll(); - - if (!hosts?.length) { - // @TODO: find a better pattern to seeding default data in indexedDB - await HostDTO.bulkAdd(DefaultHosts); - loadKnownHosts(); - } else { - const selectedHost = hosts.find(({ lastSelected }) => lastSelected) || hosts[0]; - setHostsState(s => ({ ...s, hosts, selectedHost })); - } - }, []); - - useEffect(() => { - loadKnownHosts(); - }, [loadKnownHosts]); - - useEffect(() => { - const { hosts, selectedHost } = hostsState; - - if (selectedHost?.id) { - updateLastSelectedHost(selectedHost.id).then(() => { - onChange(selectedHost); - }); - } - }, [hostsState, onChange]); - - useReduxEffect(() => { - setTestingConnection(TestConnection.SUCCESS); - }, ServerTypes.TEST_CONNECTION_SUCCESSFUL, []); - - useReduxEffect(() => { - setTestingConnection(TestConnection.FAILED); - }, ServerTypes.TEST_CONNECTION_FAILED, []); - - const selectHost = (selectedHost) => { - setHostsState(s => ({ ...s, selectedHost })); - }; - - const openAddKnownHostDialog = () => { - setDialogState(s => ({ ...s, open: true, edit: null })); - }; - - const openEditKnownHostDialog = (host: HostDTO) => { - setDialogState(s => ({ ...s, open: true, edit: host })); - }; - - const closeKnownHostDialog = () => { - setDialogState(s => ({ ...s, open: false })); - } - - const handleDialogRemove = async ({ id }) => { - setHostsState(s => ({ - ...s, - hosts: s.hosts.filter(host => host.id !== id), - selectedHost: s.selectedHost.id === id ? s.hosts[0] : s.selectedHost, - })); - - closeKnownHostDialog(); - HostDTO.delete(id); - setShowDeleteToast(true) - }; - - const handleDialogSubmit = async ({ id, name, host, port }) => { - if (id) { - const hostDTO = await HostDTO.get(id); - hostDTO.name = name; - hostDTO.host = host; - hostDTO.port = port; - await hostDTO.save(); - - setHostsState(s => ({ - ...s, - hosts: s.hosts.map(h => h.id === id ? hostDTO : h), - selectedHost: hostDTO - })); - setShowEditToast(true) - } else { - const newHost: Host = { name, host, port, editable: true }; - newHost.id = await HostDTO.add(newHost) as number; - - setHostsState(s => ({ - ...s, - hosts: [...s.hosts, newHost], - selectedHost: newHost, - })); - setShowCreateToast(true) - } - - closeKnownHostDialog(); - }; - - const updateLastSelectedHost = (hostId): Promise => { - testConnection(); - - return HostDTO.getAll().then(hosts => - hosts.map(async host => { - if (host.id === hostId) { - host.lastSelected = true; - return await host.save(); - } - - if (host.lastSelected) { - host.lastSelected = false; - return await host.save(); - } - - return host; - }) - ); - }; - - const testConnection = () => { - setTestingConnection(TestConnection.TESTING); - - const options = { ...getHostPort(hostsState.selectedHost) }; - AuthenticationService.testConnection(options); - } - - return ( - - - { touched && ( -
- { - (error && -
- {error} - -
- ) || - - (warning &&
{warning}
) - } -
- ) } - - { t('KnownHosts.label') } - -
- - - setShowCreateToast(false)}>{ t('KnownHosts.toast', { mode: 'created' }) } - setShowDeleteToast(false)}>{ t('KnownHosts.toast', { mode: 'deleted' }) } - setShowEditToast(false)}>{ t('KnownHosts.toast', { mode: 'edited' }) } -
- ); -}; - -export default KnownHosts; diff --git a/webclient/src/components/LanguageDropdown/LanguageDropdown.css b/webclient/src/components/LanguageDropdown/LanguageDropdown.css deleted file mode 100644 index c4db3d0d0..000000000 --- a/webclient/src/components/LanguageDropdown/LanguageDropdown.css +++ /dev/null @@ -1,19 +0,0 @@ -.LanguageDropdown { -} - -.LanguageDropdown-item { - display: flex; - align-items: center; -} - -.LanguageDropdown-item__image { - width: 1.5em; -} - -.MuiSelect-select .LanguageDropdown-item__label { - display: none; -} - -.MuiList-root .LanguageDropdown-item__image { - margin-right: 1em; -} diff --git a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx b/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx deleted file mode 100644 index 8f63bd549..000000000 --- a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// eslint-disable-next-line -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Select, MenuItem } from '@mui/material'; -import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; - -import { Images } from 'images/Images'; -import { Language, LanguageCountry, LanguageNative } from 'types'; - -import './LanguageDropdown.css'; - -const LanguageDropdown = () => { - const { t, i18n } = useTranslation(); - const [language, setLanguage] = useState(i18n.resolvedLanguage); - - useEffect(() => { - if (language !== i18n.resolvedLanguage) { - i18n.changeLanguage(language); - } - }, [language]); - - return ( - - - - ) -}; - -export default LanguageDropdown; diff --git a/webclient/src/components/Message/CardCallout.css b/webclient/src/components/Message/CardCallout.css deleted file mode 100644 index 0011b238a..000000000 --- a/webclient/src/components/Message/CardCallout.css +++ /dev/null @@ -1,4 +0,0 @@ -.callout { - font-weight: bold; - color: green; -} diff --git a/webclient/src/components/Message/CardCallout.tsx b/webclient/src/components/Message/CardCallout.tsx deleted file mode 100644 index d34316541..000000000 --- a/webclient/src/components/Message/CardCallout.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// eslint-disable-next-line -import React, { useMemo, useState } from 'react'; -import { styled } from '@mui/material/styles'; -import Popover from '@mui/material/Popover'; - -import { CardDTO, TokenDTO } from 'services'; - -import CardDetails from '../CardDetails/CardDetails'; -import TokenDetails from '../TokenDetails/TokenDetails'; - -import './CardCallout.css'; - -const PREFIX = 'CardCallout'; - -const classes = { - popover: `${PREFIX}-popover`, - popoverContent: `${PREFIX}-popoverContent` -}; - -const Root = styled('span')(({ theme }) => ({ - [`& .${classes.popover}`]: { - pointerEvents: 'none', - }, - - [`& .${classes.popoverContent}`]: { - pointerEvents: 'none', - } -})); - -const CardCallout = ({ name }) => { - const [card, setCard] = useState(null); - const [token, setToken] = useState(null); - const [anchorEl, setAnchorEl] = useState(null); - - useMemo(async () => { - const card = await CardDTO.get(name); - if (card) { - return setCard(card) - } - - const token = await TokenDTO.get(name); - if (token) { - return setToken(token); - } - }, [name]); - - const handlePopoverOpen = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handlePopoverClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - - return ( - - {card?.name || token?.name?.value || name} - - { - (card || token) && ( - -
- { card && () } - { token && () } -
-
- ) - } -
- ); -}; - -export default CardCallout; diff --git a/webclient/src/components/Message/Message.css b/webclient/src/components/Message/Message.css deleted file mode 100644 index cbb0df2a9..000000000 --- a/webclient/src/components/Message/Message.css +++ /dev/null @@ -1,3 +0,0 @@ -.link { - color: blue; -} diff --git a/webclient/src/components/Message/Message.tsx b/webclient/src/components/Message/Message.tsx deleted file mode 100644 index ec0b05507..000000000 --- a/webclient/src/components/Message/Message.tsx +++ /dev/null @@ -1,105 +0,0 @@ -// eslint-disable-next-line -import React, { useEffect, useMemo, useState } from 'react'; - -import { NavLink, generatePath } from 'react-router-dom'; - -import { - RouteEnum, - URL_REGEX, - MESSAGE_SENDER_REGEX, - MENTION_REGEX, - CARD_CALLOUT_REGEX, - CALLOUT_BOUNDARY_REGEX, -} from 'types'; - -import CardCallout from './CardCallout'; -import './Message.css'; - -const Message = ({ message: { message, messageType, timeOf, timeReceived } }) => ( -
-
- -
-
-); - -const ParsedMessage = ({ message }) => { - const [messageChunks, setMessageChunks] = useState(null); - const [name, setName] = useState(null); - - useMemo(() => { - const name = message.match(MESSAGE_SENDER_REGEX); - - if (name) { - setName(name[1]); - } - - setMessageChunks(parseMessage(message)); - }, [message]); - - return ( -
- { name && (:) } - { messageChunks } -
- ); -}; - -const PlayerLink = ({ name, label = name }) => ( - - {label} - -); - -function parseMessage(message) { - return message.replace(MESSAGE_SENDER_REGEX, '') - .split(CARD_CALLOUT_REGEX) - .filter(chunk => !!chunk) - .map(parseChunks); -} - -function parseChunks(chunk, index) { - if (chunk.match(CARD_CALLOUT_REGEX)) { - const name = chunk.replace(CALLOUT_BOUNDARY_REGEX, '').trim(); - return (); - } - - if (chunk.match(URL_REGEX)) { - return parseUrlChunk(chunk); - } - - if (chunk.match(MENTION_REGEX)) { - return parseMentionChunk(chunk); - } - - return chunk; -} - -function parseUrlChunk(chunk) { - return chunk.split(URL_REGEX) - .filter(urlChunk => !!urlChunk) - .map((urlChunk, index) => { - if (urlChunk.match(URL_REGEX)) { - return ({urlChunk}); - } - - return urlChunk; - }); -} - -function parseMentionChunk(chunk) { - return chunk.split(MENTION_REGEX) - .filter(mentionChunk => !!mentionChunk) - .map((mentionChunk, index) => { - const mention = mentionChunk.match(MENTION_REGEX); - - if (mention) { - const name = mention[0].substr(1); - return (); - } - - return mentionChunk; - }); -} - -export default Message; diff --git a/webclient/src/components/ScrollToBottomOnChanges/ScrollToBottomOnChanges.tsx b/webclient/src/components/ScrollToBottomOnChanges/ScrollToBottomOnChanges.tsx deleted file mode 100644 index 939a7d753..000000000 --- a/webclient/src/components/ScrollToBottomOnChanges/ScrollToBottomOnChanges.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { useEffect, useRef } from 'react'; - -const ScrollToBottomOnChanges = ({ content, changes }) => { - const messagesEndRef = useRef(null); - - // @TODO (2) - const scrollToBottom = () => { - messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) - } - - useEffect(scrollToBottom, [changes]); - - const styling = { - height: '100%' - }; - - return ( -
- {content} -
-
- ) -} - -export default ScrollToBottomOnChanges; diff --git a/webclient/src/components/SelectField/SelectField.css b/webclient/src/components/SelectField/SelectField.css deleted file mode 100644 index c59e851ae..000000000 --- a/webclient/src/components/SelectField/SelectField.css +++ /dev/null @@ -1,4 +0,0 @@ -.select-field label { - background: white; - padding: 0 5px; -} \ No newline at end of file diff --git a/webclient/src/components/SelectField/SelectField.tsx b/webclient/src/components/SelectField/SelectField.tsx deleted file mode 100644 index fdbef0e9c..000000000 --- a/webclient/src/components/SelectField/SelectField.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; - -import './SelectField.css'; - -const SelectField = ({ input, label, options, value }) => { - const id = label + '-select-field'; - const labelId = id + '-label'; - - return ( - - {label} - - - ); -}; - -export default SelectField; diff --git a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.css b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.css deleted file mode 100644 index f439dee5c..000000000 --- a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.css +++ /dev/null @@ -1,37 +0,0 @@ -.three-pane-layout, -.three-pane-layout .grid { - width: 100%; - height: 100%; - margin: 0; -} - -.three-pane-layout .grid-main, -.three-pane-layout .grid-side { - height: 100%; -} - -.three-pane-layout .grid-main { - display: flex; - flex-direction: column; -} - -.three-pane-layout .grid-main__top { - max-height: 50%; - width: 100%; - padding-bottom: 20px; - flex-shrink: 0; -} - -.three-pane-layout .grid-main__bottom { - height: 100%; - width: 100%; - flex-shrink: 1; - overflow: hidden; -} - -.three-pane-layout .grid-main__top.fixedHeight, -.three-pane-layout .grid-main__bottom.fixedHeight { - height: 50%; - overflow: visible; - padding: 0 0 16px; -} diff --git a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx deleted file mode 100644 index 7919f9d35..000000000 --- a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Component, CElement } from 'react'; -import { connect } from 'react-redux'; -import Grid from '@mui/material/Grid'; -import Hidden from '@mui/material/Hidden'; - -import './ThreePaneLayout.css'; - -// @DEPRECATED -// This component sucks balls, dont use it. It will be removed sooner than later. -class ThreePaneLayout extends Component { - render() { - return ( -
- - - - {this.props.top} - - - {this.props.bottom} - - - - - {this.props.side} - - - -
- ); - } -} - -interface ThreePaneLayoutProps { - top: CElement, - bottom: CElement, - side?: CElement, - fixedHeight?: boolean, -} - -const mapStateToProps = state => ({}); - -export default connect(mapStateToProps)(ThreePaneLayout); diff --git a/webclient/src/components/Toast/Toast.tsx b/webclient/src/components/Toast/Toast.tsx deleted file mode 100644 index 4ef8a3cad..000000000 --- a/webclient/src/components/Toast/Toast.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react' -import ReactDOM from 'react-dom' - -import Alert, { AlertProps } from '@mui/material/Alert'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import Slide, { SlideProps } from '@mui/material/Slide'; -import Snackbar from '@mui/material/Snackbar'; - -const iconMapping = { - success: -} - -function Toast(props) { - const { open, onClose, severity, autoHideDuration, children } = props - - const rootElemRef = React.useRef(document.createElement('div')); - - React.useEffect(() => { - document.body.appendChild(rootElemRef.current) - return () => { - rootElemRef.current.remove(); - } - }, [rootElemRef]) - - const handleClose = (event?: React.SyntheticEvent, reason?: string) => { - if (reason === 'clickaway') { - return; - } - onClose(event); - }; - - const node = ( - - - {children} - - - ) - if (!rootElemRef.current) { - return null - } - - return ReactDOM.createPortal( - node, - rootElemRef.current - ); -} - -Toast.defaultProps = { - severity: 'success', - // 10s wait before automatically dismissing the Toast. - autoHideDuration: 10000, -} - -function TransitionLeft(props) { - return ; -} - -export default Toast diff --git a/webclient/src/components/Toast/ToastContext.tsx b/webclient/src/components/Toast/ToastContext.tsx deleted file mode 100644 index 44753d87f..000000000 --- a/webclient/src/components/Toast/ToastContext.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { createContext, FC, PropsWithChildren, ReactChild, ReactNode, useContext, useEffect, useReducer, ContextType, Context } from 'react' - -import { ACTIONS, initialState, reducer } from './reducer'; -import Toast from './Toast' - -interface ToastEntry { - isOpen: boolean, - children: ReactChild, -} - -interface ToastState { - toasts: Map, - addToast: (key, children) => void, - openToast: (key) => void, - closeToast: (key) => void, - removeToast: (key) => void, -} - -const ToastContext: Context = createContext({ - toasts: new Map(), - addToast: (key, children) => {}, - openToast: (key) => {}, - closeToast: (key) => {}, - removeToast: (key) => {}, -}); - -export const ToastProvider: FC = (props) => { - const { children } = props - const [state, dispatch] = useReducer(reducer, initialState) - const providerState = { - toasts: state.toasts, - addToast: (key, children) => dispatch({ type: ACTIONS.ADD_TOAST, payload: { key, children } }), - openToast: key => dispatch({ type: ACTIONS.OPEN_TOAST, payload: { key } }), - closeToast: key => dispatch({ type: ACTIONS.CLOSE_TOAST, payload: { key } }), - removeToast: key => dispatch({ type: ACTIONS.REMOVE_TOAST, payload: { key } }), - } - return ( - - {children} -
- {Array.from(state.toasts).map(([key, value]) => { - const { isOpen, children } = value; - return ( - dispatch({ type: ACTIONS.CLOSE_TOAST, payload: { key } })}> - {children} - - ) - })} -
-
- ) -} - -export interface ToastHookOptions { - key: string, - children: ReactNode -} - -export function useToast({ key, children }) { - const { addToast, openToast, closeToast, removeToast } = useContext(ToastContext) - - useEffect(() => { - addToast(key, children) - }, []) - - return { - openToast: () => openToast(key), - closeToast: () => closeToast(key), - removeToast: () => removeToast(key), - } -} diff --git a/webclient/src/components/Toast/index.ts b/webclient/src/components/Toast/index.ts deleted file mode 100644 index 40e7c7368..000000000 --- a/webclient/src/components/Toast/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useToast, ToastProvider } from './ToastContext'; -import Toast from './Toast'; - -export { - Toast as default, - useToast, - ToastProvider, -} diff --git a/webclient/src/components/Toast/reducer.ts b/webclient/src/components/Toast/reducer.ts deleted file mode 100644 index 3f600db52..000000000 --- a/webclient/src/components/Toast/reducer.ts +++ /dev/null @@ -1,61 +0,0 @@ -export const ACTIONS = { - ADD_TOAST: 'ADD_TOAST', - OPEN_TOAST: 'OPEN_TOAST', - CLOSE_TOAST: 'CLOSE_TOAST', - REMOVE_TOAST: 'REMOVE_TOAST', -} - -export const initialState = { - toasts: {} -} - -export function reducer(state, { type, payload }) { - const { key, children } = payload; - - switch (type) { - case ACTIONS.ADD_TOAST: { - return { - ...state, - toasts: { - ...state.toasts, - [key]: { - isOpen: false, - children, - }, - }, - }; - } - case ACTIONS.OPEN_TOAST: { - return { - ...state, - toasts: { - ...state.toasts, - [key]: { - ...state.toasts[key], - isOpen: true, - }, - }, - }; - } - case ACTIONS.CLOSE_TOAST: { - return { - ...state, - toasts: { - ...state.toasts, - [key]: { - ...state.toasts[key], - isOpen: false, - }, - }, - }; - } - case ACTIONS.REMOVE_TOAST: { - const newState = { ...state }; - delete newState.toasts[key]; - - return newState; - } - default: - throw Error('Please pick an available action') - } -} diff --git a/webclient/src/components/Token/Token.css b/webclient/src/components/Token/Token.css deleted file mode 100644 index 08f18b872..000000000 --- a/webclient/src/components/Token/Token.css +++ /dev/null @@ -1,4 +0,0 @@ -.token { - width: 100%; - height: 100%; -} diff --git a/webclient/src/components/Token/Token.tsx b/webclient/src/components/Token/Token.tsx deleted file mode 100644 index 29b39ecc5..000000000 --- a/webclient/src/components/Token/Token.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// eslint-disable-next-line -import React, { useMemo, useState } from 'react'; - -import { TokenDTO } from 'services'; - -import './Token.css'; - -interface TokenProps { - token: TokenDTO; -} - -const Token = ({ token }: TokenProps) => { - const set = Array.isArray(token?.set) ? token?.set[0] : token?.set; - return token && ( - {token?.name?.value} - ); -} - -export default Token; diff --git a/webclient/src/components/TokenDetails/TokenDetails.css b/webclient/src/components/TokenDetails/TokenDetails.css deleted file mode 100644 index 3c3267839..000000000 --- a/webclient/src/components/TokenDetails/TokenDetails.css +++ /dev/null @@ -1,46 +0,0 @@ -.tokenDetails { - padding: 10px; - width: calc(400px * .716); - font-size: 10px; -} - -.tokenDetails-token { - height: 400px; - margin: 0 auto; -} - -.tokenDetails-attribute { - display: flex; - justify-content: space-between; - align-items: baseline; -} - -.tokenDetails-attributes { - margin-top: 10px; -} - -.tokenDetails-attribute__label { - text-transform: uppercase; - font-size: 10px; - margin-right: 10px; -} - -.tokenDetails-attribute__value { - text-align: right; -} - -.tokenDetails-text { - font-size: 12px; - margin-top: 10px; - padding: 5px; - background: rgba(0, 0, 0, .15); - white-space: pre-line; -} - -.tokenDetails-text__flavor { - font-style: italic; -} - -.tokenDetails-text__current:not(:empty) + .tokenDetails-text__flavor { - margin-top: 10px; -} diff --git a/webclient/src/components/TokenDetails/TokenDetails.tsx b/webclient/src/components/TokenDetails/TokenDetails.tsx deleted file mode 100644 index f3d8aed96..000000000 --- a/webclient/src/components/TokenDetails/TokenDetails.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// eslint-disable-next-line -import React, { useMemo, useState } from 'react'; - -import { TokenDTO } from 'services'; - -import Token from '../Token/Token'; - -import './TokenDetails.css'; - -interface TokenProps { - token: TokenDTO; -} - -const TokenDetails = ({ token }: TokenProps) => { - const props = token?.prop?.value; - - return ( -
-
- -
- - { - token && ( -
-
-
- Name: - {token.name?.value} -
- - { - (!props.pt?.value) ? null : ( -
- P/T: - {props.pt.value} -
- ) - } - - { - !props.colors?.value ? null : ( -
- Color(s): - {props.colors.value} -
- ) - } - - { - !props.maintype?.value ? null : ( -
- Main Type: - {props.maintype.value} -
- ) - } - - { - !props.type?.value ? null : ( -
- Type: - {props.type.value} -
- ) - } -
- - { - !token.text?.value ? null : ( -
-
- {token.text.value} -
-
- ) - } -
- ) - } - -
- ); -} - -export default TokenDetails; diff --git a/webclient/src/components/UserDisplay/UserDisplay.css b/webclient/src/components/UserDisplay/UserDisplay.css deleted file mode 100644 index 0697c7f68..000000000 --- a/webclient/src/components/UserDisplay/UserDisplay.css +++ /dev/null @@ -1,16 +0,0 @@ -.user-display, -.user-display__link { - height: 100%; - width: 100%; -} - -.user-display__details { - height: 100%; - display: flex; - align-items: center; -} - -.user-display__country { - width: 1.1em; - margin-right: 0.4em; -} diff --git a/webclient/src/components/UserDisplay/UserDisplay.tsx b/webclient/src/components/UserDisplay/UserDisplay.tsx deleted file mode 100644 index 9c45a0f51..000000000 --- a/webclient/src/components/UserDisplay/UserDisplay.tsx +++ /dev/null @@ -1,150 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import { NavLink, generatePath } from 'react-router-dom'; - -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; - -import { Images } from 'images/Images'; -import { SessionService } from 'api'; -import { ServerSelectors } from 'store'; -import { RouteEnum, User } from 'types'; - -import './UserDisplay.css'; - - -class UserDisplay extends Component { - constructor(props) { - super(props); - - this.handleClick = this.handleClick.bind(this); - this.handleClose = this.handleClose.bind(this); - this.navigateToUserProfile = this.navigateToUserProfile.bind(this); - this.addToBuddyList = this.addToBuddyList.bind(this); - this.removeFromBuddyList = this.removeFromBuddyList.bind(this); - this.addToIgnoreList = this.addToIgnoreList.bind(this); - this.removeFromIgnoreList = this.removeFromIgnoreList.bind(this); - - this.isABuddy = this.isABuddy.bind(this); - this.isIgnored = this.isIgnored.bind(this); - - this.state = { - position: null - }; - } - - handleClick(event) { - event.preventDefault(); - - this.setState({ - position: { - x: event.clientX + 2, - y: event.clientY + 4, - } - }); - } - - handleClose() { - this.setState({ - position: null - }); - } - - navigateToUserProfile() { - this.handleClose(); - } - - addToBuddyList() { - SessionService.addToBuddyList(this.props.user.name); - this.handleClose(); - } - - removeFromBuddyList() { - SessionService.removeFromBuddyList(this.props.user.name); - this.handleClose(); - } - - addToIgnoreList() { - SessionService.addToIgnoreList(this.props.user.name); - this.handleClose(); - } - - removeFromIgnoreList() { - SessionService.removeFromIgnoreList(this.props.user.name); - this.handleClose(); - } - - isABuddy() { - return this.props.buddyList.filter(user => user.name === this.props.user.name).length; - } - - isIgnored() { - return this.props.ignoreList.filter(user => user.name === this.props.user.name).length; - } - - render() { - const { user } = this.props; - const { position } = this.state; - const { name, country } = user; - - const isABuddy = this.isABuddy(); - const isIgnored = this.isIgnored(); - - // console.log('user', name, !!isABuddy, !!isIgnored); - - return ( -
- -
- {country} -
{name}
-
-
-
- - - Chat - - { - !isABuddy - ? (Add to Buddy List) - : (Remove From Buddy List) - } - { - !isIgnored - ? (Add to Ignore List) - : (Remove From Ignore List) - } - -
-
- ); - } -} - -interface UserDisplayProps { - user: User; - buddyList: User[]; - ignoreList: User[]; -} - -interface UserDisplayState { - position: any; -} - -const mapStateToProps = (state) => ({ - buddyList: ServerSelectors.getBuddyList(state), - ignoreList: ServerSelectors.getIgnoreList(state) -}); - -export default connect(mapStateToProps)(UserDisplay); diff --git a/webclient/src/components/VirtualList/VirtualList.css b/webclient/src/components/VirtualList/VirtualList.css deleted file mode 100644 index 330cd5a90..000000000 --- a/webclient/src/components/VirtualList/VirtualList.css +++ /dev/null @@ -1,3 +0,0 @@ -.virtual-list { - height: 100%; -} \ No newline at end of file diff --git a/webclient/src/components/VirtualList/VirtualList.tsx b/webclient/src/components/VirtualList/VirtualList.tsx deleted file mode 100644 index e40ef712c..000000000 --- a/webclient/src/components/VirtualList/VirtualList.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// eslint-disable-next-line -import React from "react"; - -import { FixedSizeList as List } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; - -import './VirtualList.css'; - -const VirtualList = ({ items, itemKey, className = {}, size = 30 }) => ( -
- - {({ height, width }) => ( - - {Row} - - )} - -
-); - -const Row = ({ data, index, style }) => ( -
- {data[index]} -
-); - -export default VirtualList; diff --git a/webclient/src/components/index.ts b/webclient/src/components/index.ts deleted file mode 100644 index 2d67b3003..000000000 --- a/webclient/src/components/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Common components -export { default as Card } from './Card/Card'; -export { default as CardDetails } from './CardDetails/CardDetails'; -export { default as CountryDropdown } from './CountryDropdown/CountryDropdown'; -export { default as InputField } from './InputField/InputField'; -export { default as InputAction } from './InputAction/InputAction'; -export { default as KnownHosts } from './KnownHosts/KnownHosts'; -export { default as LanguageDropdown } from './LanguageDropdown/LanguageDropdown'; -export { default as Message } from './Message/Message'; -export { default as VirtualList } from './VirtualList/VirtualList'; -export { default as UserDisplay } from './UserDisplay/UserDisplay'; -export { default as ThreePaneLayout } from './ThreePaneLayout/ThreePaneLayout'; -export { default as CheckboxField } from './CheckboxField/CheckboxField'; -export { default as SelectField } from './SelectField/SelectField'; -export { default as ScrollToBottomOnChanges } from './ScrollToBottomOnChanges/ScrollToBottomOnChanges'; - -// Guards -export { default as AuthGuard } from './Guard/AuthGuard'; -export { default as ModGuard } from './Guard/ModGuard'; diff --git a/webclient/src/containers/Account/Account.css b/webclient/src/containers/Account/Account.css deleted file mode 100644 index 6b5c995a9..000000000 --- a/webclient/src/containers/Account/Account.css +++ /dev/null @@ -1,53 +0,0 @@ -.account { - display: flex; - justify-content: space-between; - height: 100%; - padding: 5px; -} - -.account-column { - display: flex; - flex-direction: column; - width: 33%; -} - - -.account-list { - display: flex; - flex-direction: column; - height: 100%; - padding: 20px; -} - -.account-details { - display: flex; - flex-direction: column; - align-items: center; - padding: 20px; -} - - -.account-details__actions { - display: flex; - align-items: stretch; - justify-content: space-around; - width: 100%; -} - -.account-details p { - margin-bottom: 10px; -} - -.account-details button { - margin-top: 10px; - font-size: 10px; -} - -.account-details > img { - width: 100%; - margin-bottom: 20px; -} - -.account-details__lang { - margin-top: 20px; -} diff --git a/webclient/src/containers/Account/Account.tsx b/webclient/src/containers/Account/Account.tsx deleted file mode 100644 index 0c4e001a0..000000000 --- a/webclient/src/containers/Account/Account.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; - -import Button from '@mui/material/Button'; -import ListItem from '@mui/material/ListItem'; -import Paper from '@mui/material/Paper'; - -import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from 'components'; -import { AuthenticationService, SessionService } from 'api'; -import { ServerSelectors } from 'store'; -import { User } from 'types'; -import Layout from 'containers/Layout/Layout'; - -import AddToBuddies from './AddToBuddies'; -import AddToIgnore from './AddToIgnore'; - -import './Account.css'; - -const Account = (props: AccountProps) => { - const { buddyList, ignoreList, serverName, serverVersion, user } = props; - const { country, realName, name, userLevel, accountageSecs, avatarBmp } = user || {}; - let url = URL.createObjectURL(new Blob([avatarBmp], { 'type': 'image/png' })); - - const { t } = useTranslation(); - - const handleAddToBuddies = ({ userName }) => { - SessionService.addToBuddyList(userName); - }; - - const handleAddToIgnore = ({ userName }) => { - SessionService.addToIgnoreList(userName); - }; - - return ( - - -
- -
- Buddies Online: ?/{buddyList.length} -
- buddyList[index].name } - items={ buddyList.map(user => ( - - - - )) } - /> -
- -
-
-
-
- -
- Ignored Users Online: ?/{ignoreList.length} -
- ignoreList[index].name } - items={ ignoreList.map(user => ( - - - - )) } - /> -
- -
-
-
-
- - {name} -

{name}

-

Location: ({country?.toUpperCase()})

-

User Level: {userLevel}

-

Account Age: {accountageSecs}

-

Real Name: {realName}

-
- - - -
- -
- -

Server Name: {serverName}

-

Server Version: {serverVersion}

- - -
- -
-
-
-
- ) -} - -interface AccountProps { - buddyList: User[]; - ignoreList: User[]; - serverName: string; - serverVersion: string; - user: User; -} - -const mapStateToProps = state => ({ - buddyList: ServerSelectors.getBuddyList(state), - ignoreList: ServerSelectors.getIgnoreList(state), - serverName: ServerSelectors.getName(state), - serverVersion: ServerSelectors.getVersion(state), - user: ServerSelectors.getUser(state), -}); - -export default connect(mapStateToProps)(Account); diff --git a/webclient/src/containers/Account/AddToBuddies.tsx b/webclient/src/containers/Account/AddToBuddies.tsx deleted file mode 100644 index 3aa5489ad..000000000 --- a/webclient/src/containers/Account/AddToBuddies.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { Form } from 'react-final-form' - -import { InputAction } from 'components'; - -const AddToBuddies = ({ onSubmit }) => ( -
onSubmit(values)}> - {({ handleSubmit }) => ( - - - - )} - -); - -export default AddToBuddies; diff --git a/webclient/src/containers/Account/AddToIgnore.tsx b/webclient/src/containers/Account/AddToIgnore.tsx deleted file mode 100644 index 270036946..000000000 --- a/webclient/src/containers/Account/AddToIgnore.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { Form } from 'react-final-form' - -import { InputAction } from 'components'; - -const AddToIgnore = ({ onSubmit }) => ( -
onSubmit(values)}> - {({ handleSubmit }) => ( - - - - )} - -); - -export default AddToIgnore; diff --git a/webclient/src/containers/App/AppShell.css b/webclient/src/containers/App/AppShell.css deleted file mode 100644 index 4492a6bcf..000000000 --- a/webclient/src/containers/App/AppShell.css +++ /dev/null @@ -1,10 +0,0 @@ -.AppShell, -.AppShell-routes { - height: 100%; -} - -.AppShell { - display: flex; - flex-direction: column; - min-width: 768px; -} \ No newline at end of file diff --git a/webclient/src/containers/App/AppShell.tsx b/webclient/src/containers/App/AppShell.tsx deleted file mode 100644 index c28d20067..000000000 --- a/webclient/src/containers/App/AppShell.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, Suspense } from 'react'; -import { Provider } from 'react-redux'; -import { MemoryRouter as Router } from 'react-router-dom'; -import CssBaseline from '@mui/material/CssBaseline'; -import { store } from 'store'; -import Routes from './AppShellRoutes'; -import FeatureDetection from './FeatureDetection'; - -import './AppShell.css'; - -import { ToastProvider } from 'components/Toast' - -class AppShell extends Component { - componentDidMount() { - // @TODO (1) - window.onbeforeunload = () => true; - } - - handleContextMenu(event) { - event.preventDefault(); - } - - render() { - return ( - - - - -
- - - - -
-
-
-
- ); - } -} - -export default AppShell; diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx deleted file mode 100644 index c80f74c30..000000000 --- a/webclient/src/containers/App/AppShellRoutes.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; - -import { RouteEnum } from 'types'; -import { - Account, - Decks, - Game, - Player, - Room, - Server, - Login, - Logs, - Initialize, - Unsupported -} from 'containers'; - -const AppShellRoutes = () => ( -
- - } /> - - } /> - } /> - } /> - } /> - } /> - {} />} - } /> - } /> - } /> - -
-); - -export default AppShellRoutes; diff --git a/webclient/src/containers/App/FeatureDetection.tsx b/webclient/src/containers/App/FeatureDetection.tsx deleted file mode 100644 index ed9e1f241..000000000 --- a/webclient/src/containers/App/FeatureDetection.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Navigate } from 'react-router-dom'; -import { dexieService } from 'services'; -import { RouteEnum } from 'types'; - -const FeatureDetection = () => { - const [unsupported, setUnsupported] = useState(false); - - useEffect(() => { - const features: Promise[] = [ - detectIndexedDB(), - ]; - - Promise.all(features).catch((e) => setUnsupported(true)); - }, []); - - return unsupported - ? - : <>; - - function detectIndexedDB() { - return dexieService.testConnection(); - } -}; - -export default FeatureDetection; diff --git a/webclient/src/containers/Decks/Decks.css b/webclient/src/containers/Decks/Decks.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/webclient/src/containers/Decks/Decks.tsx b/webclient/src/containers/Decks/Decks.tsx deleted file mode 100644 index 190196dc9..000000000 --- a/webclient/src/containers/Decks/Decks.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; - -import { AuthGuard } from 'components/index'; -import Layout from 'containers/Layout/Layout'; - -import './Decks.css'; - -class Decks extends Component { - render() { - return ( - - - "Decks" - - ) - } -} - -export default Decks; diff --git a/webclient/src/containers/Game/Game.css b/webclient/src/containers/Game/Game.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/webclient/src/containers/Game/Game.tsx b/webclient/src/containers/Game/Game.tsx deleted file mode 100644 index 694ffb46d..000000000 --- a/webclient/src/containers/Game/Game.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; - -import { AuthGuard } from 'components'; -import Layout from 'containers/Layout/Layout'; - -import './Game.css'; - -class Game extends Component { - render() { - return ( - - - "Game" - - ) - } -} - -export default Game; diff --git a/webclient/src/containers/Initialize/Initialize.css b/webclient/src/containers/Initialize/Initialize.css deleted file mode 100644 index d52eafd5f..000000000 --- a/webclient/src/containers/Initialize/Initialize.css +++ /dev/null @@ -1,88 +0,0 @@ -.Initialize { - position: relative; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - text-align: center; -} - -.Initialize img { - width: 60px; -} - -h6.subtitle { - margin: 20px 0 10px; -} - -.Initialize-graphics { - position: absolute; - height: 100%; - width: 100%; - overflow: hidden; -} - -.Initialize-graphics__square { - position: absolute; - border: 2px solid; - opacity: .05; -} - -.Initialize-graphics__bar { - position: absolute; - opacity: .05; - border-radius: 8px; -} - -.Initialize-graphics__square.topLeft { - transform: rotate(27deg); - top: 38px; - left: 64px; - height: 134px; - width: 100px; - border-radius: 8px; -} - -.Initialize-graphics__square.topRight { - transform: rotate(10deg); - top: 74px; - right: 62px; - height: 50px; - width: 66px; - border-radius: 20px; -} - -.Initialize-graphics__square.bottomLeft { - transform: rotate(120deg); - bottom: 61px; - left: 66px; - height: 50px; - width: 66px; - border-radius: 20px; -} - -.Initialize-graphics__square.bottomRight { - transform: rotate(-24deg); - bottom: 54px; - right: 0; - height: 88px; - width: 66px; - border-radius: 8px; -} - -.Initialize-graphics__bar.bottomBar { - transform: rotate(30deg); - bottom: -4px; - left: -29px; - height: 50px; - width: 222px; -} - -.Initialize-graphics__bar.topBar { - transform: rotate(-330deg); - top: 10px; - right: -49px; - height: 50px; - width: 222px; -} diff --git a/webclient/src/containers/Initialize/Initialize.i18n.json b/webclient/src/containers/Initialize/Initialize.i18n.json deleted file mode 100644 index 77ca5e8cd..000000000 --- a/webclient/src/containers/Initialize/Initialize.i18n.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "InitializeContainer": { - "title": "DID YOU KNOW", - "subtitle": "<1>Cockatrice is run by volunteers<1>that love card games!" - } -} diff --git a/webclient/src/containers/Initialize/Initialize.tsx b/webclient/src/containers/Initialize/Initialize.tsx deleted file mode 100644 index bc777f065..000000000 --- a/webclient/src/containers/Initialize/Initialize.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useState } from 'react'; -import { styled } from '@mui/material/styles'; -import { useTranslation, Trans } from 'react-i18next'; -import { connect } from 'react-redux'; -import { Navigate } from 'react-router-dom'; -import Typography from '@mui/material/Typography'; - -import { Images } from 'images'; -import { ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import Layout from 'containers/Layout/Layout'; - -import './Initialize.css'; - -const PREFIX = 'Initialize'; - -const classes = { - root: `${PREFIX}-root` -}; - -const Root = styled('div')(({ theme }) => ({ - [`&.${classes.root}`]: { - '& .Initialize-graphics': { - color: theme.palette.primary.contrastText, - }, - - '& .Initialize-graphics__bar': { - backgroundColor: theme.palette.primary.contrastText, - }, - } -})); - -const Initialize = ({ initialized }: InitializeProps) => { - const { t } = useTranslation(); - - return initialized - ? - : ( - - -
- logo - { t('InitializeContainer.title') } - - - - -
- -
-
-
-
-
-
-
-
- - - ); -} - -interface InitializeProps { - initialized: boolean; -} - -const mapStateToProps = state => ({ - initialized: ServerSelectors.getInitialized(state), -}); - -export default connect(mapStateToProps)(Initialize); diff --git a/webclient/src/containers/Layout/Layout.css b/webclient/src/containers/Layout/Layout.css deleted file mode 100644 index a98cba47c..000000000 --- a/webclient/src/containers/Layout/Layout.css +++ /dev/null @@ -1,31 +0,0 @@ -.layout { - height: 100%; - max-height: 100%; - width: 100%; - max-width: 100%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; -} - -.layout--no-height-limit { - height: initial; - max-height: initial; -} - -.bottom-bar__container { - background: #555; - height: 50px; - width: 100%; -} - -.page__body { - flex: 1; - max-height: calc(100% - 50px); -} - -.page { - display: flex; - flex-flow: column; - width: 100%; -} diff --git a/webclient/src/containers/Layout/Layout.tsx b/webclient/src/containers/Layout/Layout.tsx deleted file mode 100644 index 6d04172ba..000000000 --- a/webclient/src/containers/Layout/Layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import LeftNav from './LeftNav'; - -import './Layout.css' - -function Layout(props:LayoutProps) { - const { children, className, showNav = true, noHeightLimit = false } = props; - const containerClasses = ['layout'] - if (noHeightLimit === true) { - containerClasses.push('layout--no-height-limit') - } - - return ( -
- {showNav && } -
-
- {children} -
- {showNav && } -
-
- ) -} - -function BottomBar(props) { - return ( -
-
- ) -} - -interface LayoutProps { - showNav?: boolean; - children: any; - className?: string; - noHeightLimit?: boolean -} - -export default Layout; diff --git a/webclient/src/containers/Layout/LeftNav.css b/webclient/src/containers/Layout/LeftNav.css deleted file mode 100644 index 85c851d2e..000000000 --- a/webclient/src/containers/Layout/LeftNav.css +++ /dev/null @@ -1,128 +0,0 @@ -.LeftNav__container { - background: #7033DB; - width: 100px; - min-width: 100px; - height: 100%; -} - -.LeftNav__logo { - display: flex; - align-items: center; - justify-content: center; - padding: 16px 0; -} - -.LeftNav__logo a { - line-height: 1; -} - -.LeftNav__logo img { - height: 32px; -} - -.LeftNav-content { - color: white; -} - -.LeftNav-serverDetails { - font-size: 12px; -} - -.LeftNav-server__indicator { - display: inline-block; - height: 12px; - width: 12px; - background: red; - border: 1px solid; - border-radius: 50%; - margin-left: 10px; -} - -.LeftNav-nav { -} - -.LeftNav-nav__links { - display: flex; - flex-flow: column; - align-items: center; - gap: 16px; -} - -.LeftNav-nav__link { - position: relative; - height: 100%; -} - -.LeftNav-nav__link:hover { - background: rgba(0, 0, 0, .125); -} - -.LeftNav-nav__link:hover .LeftNav-nav__link-menu { - display: block; -} - -.LeftNav-nav__link-btn { - display: flex; - height: 100%; - width: 100%; - align-items: center; - padding: 5px 20px; - font-weight: bold; -} - -.LeftNav-nav__link-btn__icon { - margin-left: 5px; -} - -.LeftNav-nav__link-menu { - display: none; - position: absolute; - bottom: 0; - transform: translateY(100%); - min-width: 150px; - background: #3f51b5; - box-shadow: 1px 1px 2px 0px black; - z-index: 1; -} - -.LeftNav-nav__link-menu__item { - padding: 0 !important; -} - -.LeftNav-nav__link-menu__btn { - padding: 6px 16px; - width: 100%; - color: white; - display: flex; - justify-content: space-between; -} - -.LeftNav-nav__actions { - display: flex; - justify-content: center; -} - -.LeftNav-nav__action { - -} - -.LeftNav-nav__action button { - color: white; -} - -.temp-subnav__rooms { - display: flex; - align-items: center; - font-size: 10px; - padding: 5px; -} - -.temp-chip { - margin-left: 5px; - text-decoration: none; -} - - -.temp-chip > div { - cursor: inherit; -} diff --git a/webclient/src/containers/Layout/LeftNav.tsx b/webclient/src/containers/Layout/LeftNav.tsx deleted file mode 100644 index a81eb6ce2..000000000 --- a/webclient/src/containers/Layout/LeftNav.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { connect } from 'react-redux'; -import { NavLink, useNavigate, generatePath } from 'react-router-dom'; -import IconButton from '@mui/material/IconButton'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import CloseIcon from '@mui/icons-material/Close'; -import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline'; -import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; -import * as _ from 'lodash'; - -import { AuthenticationService, RoomsService } from 'api'; -import { CardImportDialog } from 'dialogs'; -import { Images } from 'images'; -import { RoomsSelectors, ServerSelectors } from 'store'; -import { Room, RouteEnum, User } from 'types'; - -import './LeftNav.css'; - -const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => { - const navigate = useNavigate(); - const [state, setState] = useState({ - anchorEl: null, - showCardImportDialog: false, - options: [], - }); - - useEffect(() => { - let options: string[] = [ - 'Account', - 'Replays', - ]; - - if (user && AuthenticationService.isModerator(user)) { - options = [ - ...options, - 'Administration', - 'Logs' - ]; - } - - setState(s => ({ ...s, options })); - }, [user]); - - const handleMenuOpen = (event) => { - setState(s => ({ ...s, anchorEl: event.target })); - } - - const handleMenuItemClick = (option: string) => { - const route = RouteEnum[option.toUpperCase()]; - navigate(generatePath(route)); - } - - const handleMenuClose = () => { - setState(s => ({ ...s, anchorEl: null })); - } - - const leaveRoom = (event, roomId) => { - event.preventDefault(); - RoomsService.leaveRoom(roomId); - }; - - const openImportCardWizard = () => { - setState(s => ({ ...s, showCardImportDialog: true })); - handleMenuClose(); - } - - const closeImportCardWizard = () => { - setState(s => ({ ...s, showCardImportDialog: false })); - } - - return ( -
-
-
- - logo - - { AuthenticationService.isConnected(serverState) && ( - - ) } -
- { AuthenticationService.isConnected(serverState) && ( -
- -
- ) } -
- - -
- ); -} - -interface LeftNavProps { - serverState: number; - server: string; - user: User; - joinedRooms: Room[]; - showNav?: boolean; -} - -interface LeftNavState { - anchorEl: Element; - showCardImportDialog: boolean; - options: string[]; -} - -const mapStateToProps = state => ({ - serverState: ServerSelectors.getState(state), - server: ServerSelectors.getName(state), - user: ServerSelectors.getUser(state), - joinedRooms: RoomsSelectors.getJoinedRooms(state), -}); - -export default connect(mapStateToProps)(LeftNav); diff --git a/webclient/src/containers/Layout/logo.png b/webclient/src/containers/Layout/logo.png deleted file mode 100644 index 7ce83bd20..000000000 Binary files a/webclient/src/containers/Layout/logo.png and /dev/null differ diff --git a/webclient/src/containers/Login/Login.css b/webclient/src/containers/Login/Login.css deleted file mode 100644 index 7166187ad..000000000 --- a/webclient/src/containers/Login/Login.css +++ /dev/null @@ -1,202 +0,0 @@ -.login { - height: 100%; - padding: 50px; -} - -.login__wrapper { - display: flex; - flex-direction: column; - align-items: center; -} - -.login-content { - width: 100%; - max-width: 500px; - display: flex; - border-radius: 8px; - overflow: hidden; -} - -.login-content__header { - font-family: 'Teko', sans-serif; - font-size: 34px; - font-weight: bold; - display: flex; - align-items: center; - margin-bottom: 20px; -} - -.login-content__header img { - height: 60px; - margin-right: 15px; -} - -.login-content__form { - width: 100%; - padding: 50px 50px 33px; -} - -.login-content__form h1 { - margin-bottom: 20px; -} - -.login-form { - margin-top: 30px; -} - -.login-content__description { - display: none; - position: relative; - justify-content: center; - align-items: center; - text-align: center; - font-size: 24px; - overflow: hidden; -} - -.login-content__description-wrapper { - position: relative; - width: 70%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.login-content__description-cards { - width: 100%; - position: relative; - display: flex; - justify-content: space-between; -} - -.login-content__description-cards__card { - position: relative; - width: 34%; - padding-bottom: 46%; - border-radius: 8px; - box-shadow: 0 5px 10px 2px rgba(0,0,0,0.20); - font-weight: bold; - font-size: 16px; -} - -.login-content__description-cards__card-wrapper { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.login-content__description-cards__card img { - width: 70%; - border-radius: 50%; - margin: 21% 0 9%; -} - -.login-content__description-cards__card.leftCard { - transform: rotate(-12deg); -} - -.login-content__description-cards__card.rightCard { - transform: rotate(12deg); -} - -.login-content__description-cards__card.topCard { - width: 44%; - padding-bottom: 59%; - position: absolute; - top: 45%; - left: 50%; - transform: translate(-50%, -50%); -} - -.login-content__description-subtitle1 { - margin: 40px 0 20px; - font-size: 28px; - font-weight: bold; - -} -.login-content__description-subtitle2 { - font-size: 14px; -} -.login-content__description-square { - position: absolute; - border: 1px solid; - opacity: .1; -} -.login-content__description-bar { - position: absolute; - opacity: .1; - border-radius: 8px; -} - - -.login-content__description-square.topLeft { - transform: rotate(27deg); - top: 38px; - left: 64px; - height: 134px; - width: 100px; - border-radius: 8px; -} - -.login-content__description-square.topRight { - transform: rotate(10deg); - top: 74px; - right: 62px; - height: 50px; - width: 66px; - border-radius: 20px; -} - -.login-content__description-square.bottomLeft { - transform: rotate(120deg); - bottom: 61px; - left: 66px; - height: 50px; - width: 66px; - border-radius: 20px; -} - -.login-content__description-square.bottomRight { - transform: rotate(-24deg); - bottom: 54px; - right: 0; - height: 88px; - width: 66px; - border-radius: 8px; -} -.login-content__description-bar.bottomBar { - transform: rotate(30deg); - bottom: -4px; - left: -29px; - height: 50px; - width: 222px; -} -.login-content__description-bar.topBar { - transform: rotate(-330deg); - top: 10px; - right: -49px; - height: 50px; - width: 222px; -} -.login-footer { - margin-top: 30px; -} - -.login-footer__register { - margin-bottom: 10px; - font-weight: bold; -} - -.login-footer__language { - margin-top: 20px; -} - -.login-content__connectionStatus { - text-align: center; - margin: 20px 0; - padding: 20px; - font-weight: bold; -} diff --git a/webclient/src/containers/Login/Login.i18n.json b/webclient/src/containers/Login/Login.i18n.json deleted file mode 100644 index 13d248bb1..000000000 --- a/webclient/src/containers/Login/Login.i18n.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "A cross-platform virtual tabletop for multiplayer card games." - }, - "footer": { - "registerPrompt": "Not registered yet?", - "registerAction": "Create an account", - "credit": "Cockatrice is an open source project", - "version": "Version" - }, - "content": { - "subtitle1": "Play multiplayer card games online.", - "subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free." - }, - "toasts": { - "passwordResetSuccessToast": "Password Reset Successfully", - "accountActivationSuccess": "Account Activated Successfully" - } - } -} diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx deleted file mode 100644 index fe008375c..000000000 --- a/webclient/src/containers/Login/Login.tsx +++ /dev/null @@ -1,360 +0,0 @@ -import { useState, useCallback } from 'react'; -import { styled } from '@mui/material/styles'; -import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { Navigate } from 'react-router-dom'; -import Button from '@mui/material/Button'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; - -import { AuthenticationService } from 'api'; -import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from 'dialogs'; -import { LanguageDropdown } from 'components'; -import { LoginForm } from 'forms'; -import { useReduxEffect, useFireOnce } from 'hooks'; -import { Images } from 'images'; -import { HostDTO, serverProps } from 'services'; -import { RouteEnum, WebSocketConnectOptions, getHostPort } from 'types'; -import { ServerSelectors, ServerTypes } from 'store'; -import Layout from 'containers/Layout/Layout'; - -import './Login.css'; -import { useToast } from 'components/Toast'; - -const PREFIX = 'Login'; - -const classes = { - root: `${PREFIX}-root` -}; - -const Root = styled('div')(({ theme }) => ({ - [`&.${classes.root}`]: { - '& .login-content__header': { - color: theme.palette.success.light - }, - - '& .login-content__description': { - backgroundColor: theme.palette.primary.main, - color: theme.palette.primary.contrastText, - }, - - '& .login-content__description-bar': { - backgroundColor: theme.palette.primary.dark, - }, - - '& .login-content__description-cards__card': { - backgroundColor: theme.palette.background.paper, - color: theme.palette.primary.main, - }, - - [theme.breakpoints.up('lg')]: { - '& .login-content': { - maxWidth: '1000px', - }, - - '& .login-content__form': { - width: '50%', - }, - - '& .login-content__description': { - width: '50%', - display: 'flex', - }, - }, - } -})); - -const Login = ({ state, description, connectOptions }: LoginProps) => { - const { t } = useTranslation(); - - const isConnected = AuthenticationService.isConnected(state); - - const [rememberLogin, setRememberLogin] = useState(null); - const [dialogState, setDialogState] = useState({ - passwordResetRequestDialog: false, - resetPasswordDialog: false, - registrationDialog: false, - activationDialog: false, - }); - const [userToResetPassword, setUserToResetPassword] = useState(null); - - const passwordResetToast = useToast({ key: 'password-reset-success', children: t('LoginContainer.toasts.passwordResetSuccess') }); - const accountActivatedToast = useToast({ - key: 'account-activation-success', - children: t('LoginContainer.toasts.accountActivationSuccess') - }); - - useReduxEffect(() => { - closeRequestPasswordResetDialog(); - openResetPasswordDialog(); - }, ServerTypes.RESET_PASSWORD_REQUESTED, []); - - useReduxEffect(() => { - passwordResetToast.openToast() - closeResetPasswordDialog(); - }, ServerTypes.RESET_PASSWORD_SUCCESS, []); - - useReduxEffect(() => { - accountActivatedToast.openToast() - closeActivateAccountDialog(); - }, ServerTypes.ACCOUNT_ACTIVATION_SUCCESS, []); - - useReduxEffect(() => { - closeRegistrationDialog(); - openActivateAccountDialog(); - }, ServerTypes.ACCOUNT_AWAITING_ACTIVATION, []); - - useReduxEffect(() => { - resetSubmitButton(); - }, [ServerTypes.CONNECTION_FAILED, ServerTypes.LOGIN_FAILED], []); - - useReduxEffect(({ options: { hashedPassword } }) => { - updateHost(hashedPassword, rememberLogin); - }, ServerTypes.LOGIN_SUCCESSFUL, [rememberLogin]); - - const showDescription = () => { - return !isConnected && description?.length; - }; - - const onSubmitLogin = useCallback((loginForm) => { - setRememberLogin(loginForm); - const { userName, password, selectedHost, remember } = loginForm; - - const options: WebSocketConnectOptions = { - ...getHostPort(selectedHost), - userName, - password - }; - - if (remember && !password) { - options.hashedPassword = selectedHost.hashedPassword; - } - - AuthenticationService.login(options as WebSocketConnectOptions); - }, []); - - const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin); - - const updateHost = (hashedPassword, { selectedHost, remember, userName }) => { - HostDTO.get(selectedHost.id).then(hostDTO => { - hostDTO.remember = remember; - hostDTO.userName = remember ? userName : null; - hostDTO.hashedPassword = remember ? hashedPassword : null; - - hostDTO.save(); - }); - }; - - const handleRegistrationDialogSubmit = (registerForm) => { - setRememberLogin(registerForm); - const { userName, password, email, country, realName, selectedHost } = registerForm; - - AuthenticationService.register({ - ...getHostPort(selectedHost), - userName, - password, - email, - country, - realName, - }); - }; - - const handleAccountActivationDialogSubmit = ({ token }) => { - AuthenticationService.activateAccount({ - ...connectOptions, - token, - }); - }; - - const handleRequestPasswordResetDialogSubmit = (form) => { - const { userName, email, selectedHost } = form; - const { host, port } = getHostPort(selectedHost); - - if (email) { - AuthenticationService.resetPasswordChallenge({ userName, email, host, port }); - } else { - setUserToResetPassword(userName); - AuthenticationService.resetPasswordRequest({ userName, host, port }); - } - }; - - const handleResetPasswordDialogSubmit = ({ userName, token, newPassword, selectedHost }) => { - const { host, port } = getHostPort(selectedHost); - - AuthenticationService.resetPassword({ userName, token, newPassword, host, port }); - }; - - const skipTokenRequest = (userName) => { - setUserToResetPassword(userName); - - setDialogState(s => ({ ...s, - passwordResetRequestDialog: false, - resetPasswordDialog: true, - })); - }; - - const closeRequestPasswordResetDialog = () => { - setDialogState(s => ({ ...s, passwordResetRequestDialog: false })); - } - - const openRequestPasswordResetDialog = () => { - setDialogState(s => ({ ...s, passwordResetRequestDialog: true })); - } - - const closeResetPasswordDialog = () => { - setDialogState(s => ({ ...s, resetPasswordDialog: false })); - } - - const openResetPasswordDialog = () => { - setDialogState(s => ({ ...s, resetPasswordDialog: true })); - } - - const closeRegistrationDialog = () => { - setDialogState(s => ({ ...s, registrationDialog: false })); - } - - const openRegistrationDialog = () => { - setDialogState(s => ({ ...s, registrationDialog: true })); - } - - const closeActivateAccountDialog = () => { - setDialogState(s => ({ ...s, activationDialog: false })); - }; - - const openActivateAccountDialog = () => { - setDialogState(s => ({ ...s, activationDialog: true })); - }; - - return ( - - - { isConnected && } - -
- -
-
- logo - COCKATRICE -
- { t('LoginContainer.header.title') } - { t('LoginContainer.header.subtitle') } -
- -
- - { - showDescription() && ( - - {description} - - ) - } - -
-
- { t('LoginContainer.footer.registerPrompt') } - -
- - { t('LoginContainer.footer.credit') } - { new Date().getUTCFullYear() } - - - { - serverProps.REACT_APP_VERSION && ( - - { t('LoginContainer.footer.version') }: { serverProps.REACT_APP_VERSION } - - ) - } - -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stock Player - 1mrlee -
-
-
-
- Stock Player - CyberX -
-
-
-
- Stock Player - Gamer69 -
-
-
- { /**/} -

{ t('LoginContainer.content.subtitle1') }

-

{ t('LoginContainer.content.subtitle2') }

-
-
- -
- - - - - - - - - - - ); -} - -interface LoginProps { - state: number; - description: string; - connectOptions: WebSocketConnectOptions; -} - -const mapStateToProps = state => ({ - state: ServerSelectors.getState(state), - description: ServerSelectors.getDescription(state), - connectOptions: ServerSelectors.getConnectOptions(state), -}); - -export default connect(mapStateToProps)(Login); diff --git a/webclient/src/containers/Logs/LogResults.css b/webclient/src/containers/Logs/LogResults.css deleted file mode 100644 index 3a6cbd185..000000000 --- a/webclient/src/containers/Logs/LogResults.css +++ /dev/null @@ -1,3 +0,0 @@ -.log-results { - margin-bottom: 20px; -} \ No newline at end of file diff --git a/webclient/src/containers/Logs/LogResults.tsx b/webclient/src/containers/Logs/LogResults.tsx deleted file mode 100644 index 06abdbce6..000000000 --- a/webclient/src/containers/Logs/LogResults.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import * as _ from 'lodash'; - -import AppBar from '@mui/material/AppBar'; -import Box from '@mui/material/Box'; -import Paper from '@mui/material/Paper'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Tab from '@mui/material/Tab'; -import Tabs from '@mui/material/Tabs'; -import Typography from '@mui/material/Typography'; - -import './LogResults.css'; - -const LogResults = (props) => { - const { logs } = props; - - const hasRoomLogs = logs.room && logs.room.length; - const hasGameLogs = logs.game && logs.game.length; - const hasChatLogs = logs.chat && logs.chat.length; - - const [value, setValue] = React.useState(0); - - const handleChange = (event, newValue) => { - setValue(newValue); - }; - - const headerCells = [ - { - label: 'Time' - }, - { - label: 'Sender Name' - }, - { - label: 'Sender IP' - }, - { - label: 'Message' - }, - { - label: 'Target ID' - }, - { - label: 'Target Name' - } - ]; - - return ( -
- - - - - - - - - - - - - - - - -
- ) -}; - -const a11yProps = index => { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; -}; - -const TabPanel = ({ children, value, index, ...other }) => { - return ( - - ); -}; - -const Results = ({ headerCells, logs }) => ( - - - - - { _.map(headerCells, ({ label }) => ( - {label} - ))} - - - - { _.map(logs, ({ time, senderName, senderIp, message, targetId, targetName }, index) => ( - - {time} - {senderName} - {senderIp} - {message} - {targetId} - {targetName} - - ))} - -
-
-); - -export default LogResults; diff --git a/webclient/src/containers/Logs/Logs.css b/webclient/src/containers/Logs/Logs.css deleted file mode 100644 index 37ac8eb03..000000000 --- a/webclient/src/containers/Logs/Logs.css +++ /dev/null @@ -1,14 +0,0 @@ -.moderator-logs { - height: 100%; - display: flex; - padding: 20px; -} - -.moderator-logs__form { - width: 40%; - margin-right: 20px; -} - -.moderator-logs__results { - width: 100%; -} \ No newline at end of file diff --git a/webclient/src/containers/Logs/Logs.tsx b/webclient/src/containers/Logs/Logs.tsx deleted file mode 100644 index a34a7b008..000000000 --- a/webclient/src/containers/Logs/Logs.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import * as _ from 'lodash'; - -import { ModeratorService } from 'api'; -import { AuthGuard, ModGuard } from 'components'; -import { SearchForm } from 'forms'; -import { ServerDispatch, ServerSelectors, ServerStateLogs } from 'store'; -import { LogFilters } from 'types'; - -import LogResults from './LogResults'; -import './Logs.css'; - -class Logs extends Component { - MAXIMUM_RESULTS = 1000; - - constructor(props) { - super(props); - - this.onSubmit = this.onSubmit.bind(this); - } - - componentWillUnmount() { - ServerDispatch.clearLogs(); - } - - onSubmit(fields: LogFilters) { - const trimmedFields: any = this.trimFields(fields); - - const { userName, ipAddress, gameName, gameId, message, logLocation } = trimmedFields; - - const required = _.filter({ - userName, ipAddress, gameName, gameId, message - }, field => field); - - if (logLocation) { - trimmedFields.logLocation = this.flattenLogLocations(logLocation); - } - - trimmedFields.maximumResults = this.MAXIMUM_RESULTS; - - if (_.size(required)) { - ModeratorService.viewLogHistory(trimmedFields); - } else { - // @TODO use yet-to-be-implemented banner/alert - } - } - - private trimFields(fields) { - return _.reduce(fields, (obj, field, key) => { - if (typeof field === 'string') { - const trimmed = _.trim(field); - - if (!!trimmed) { - obj[key] = trimmed; - } - } else { - obj[key] = field; - } - - return obj; - }, {}); - } - - private flattenLogLocations(logLocations) { - return _.reduce(logLocations, (arr, loc, key) => { - arr.push(key); - return arr; - }, []) - } - - render() { - return ( -
- - - -
- -
- -
- -
-
- ) - } -} - -interface LogsTypes { - logs: ServerStateLogs -} - -const mapStateToProps = state => ({ - logs: ServerSelectors.getLogs(state) -}); - -export default connect(mapStateToProps)(Logs); - - - - - - - - - - diff --git a/webclient/src/containers/Player/Player.tsx b/webclient/src/containers/Player/Player.tsx deleted file mode 100644 index 74feff8bd..000000000 --- a/webclient/src/containers/Player/Player.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import Layout from 'containers/Layout/Layout'; - -import { AuthGuard } from 'components'; - -class Player extends Component { - render() { - return ( - - - "Player" - - ) - } -} - -export default Player; diff --git a/webclient/src/containers/Room/Games.css b/webclient/src/containers/Room/Games.css deleted file mode 100644 index 623ab47f5..000000000 --- a/webclient/src/containers/Room/Games.css +++ /dev/null @@ -1,30 +0,0 @@ -.games { -} - -.games-header, -.game { - display: flex; - padding: 10px; - border-bottom: 1px solid black; -} - -.games-header__cell { - max-width: 200px; -} - -.games-header__label, -.game__detail { - width: 10%; - flex-grow: 0; -} - -.games-header__label.description, -.game__detail.description { - width: 20%; - flex-grow: 1; -} - -.games-header__label.creator, -.game__detail.creator { - width: 20%; -} diff --git a/webclient/src/containers/Room/Games.tsx b/webclient/src/containers/Room/Games.tsx deleted file mode 100644 index 67a9239d3..000000000 --- a/webclient/src/containers/Room/Games.tsx +++ /dev/null @@ -1,143 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import * as _ from 'lodash'; - -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import Tooltip from '@mui/material/Tooltip'; - -// import { RoomsService } from "AppShell/common/services"; - -import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; -import { UserDisplay } from 'components'; - -import './Games.css'; - -// @TODO run interval to update timeSinceCreated -class Games extends Component { - private headerCells = [ - { - label: 'Age', - field: 'startTime' - }, - { - label: 'Description', - field: 'description' - }, - { - label: 'Creator', - field: 'creatorInfo.name' - }, - { - label: 'Type', - field: 'gameType' - }, - { - label: 'Restrictions', - // field: "?" - }, - { - label: 'Players', - // field: ["maxPlayers", "playerCount"] - }, - { - label: 'Spectators', - field: 'spectatorsCount' - }, - ]; - - handleSort(sortByField) { - const { room: { roomId }, sortBy } = this.props; - const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); - RoomsDispatch.sortGames(roomId, field, order); - } - - private isUnavailableGame({ started, maxPlayers, playerCount }) { - return !started && playerCount < maxPlayers; - } - - private isPasswordProtectedGame({ withPassword }) { - return !withPassword; - } - - private isBuddiesOnlyGame({ onlyBuddies }) { - return !onlyBuddies; - } - - render() { - const { room, sortBy } = this.props; - - const games = room.gameList.filter(game => ( - this.isUnavailableGame(game) && - this.isPasswordProtectedGame(game) && - this.isBuddiesOnlyGame(game) - )); - - return ( -
- - - - { _.map(this.headerCells, ({ label, field }) => { - const active = field === sortBy.field; - const order = sortBy.order.toLowerCase(); - const sortDirection = active ? order : false; - - return ( - - {!field ? label : ( - this.handleSort(field)} - > - {label} - - )} - - ); - })} - - - - { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( - - {startTime} - - -
- {description} -
-
-
- - - - {gameType} - ? - {`${playerCount}/${maxPlayers}`} - {spectatorsCount} -
- ))} -
-
-
- ); - } -} - -interface GamesProps { - room: any; - sortBy: any; -} - -const mapStateToProps = state => ({ - sortBy: RoomsSelectors.getSortGamesBy(state) -}); - -export default connect(mapStateToProps)(Games); diff --git a/webclient/src/containers/Room/Messages.css b/webclient/src/containers/Room/Messages.css deleted file mode 100644 index 731002cb8..000000000 --- a/webclient/src/containers/Room/Messages.css +++ /dev/null @@ -1,17 +0,0 @@ -.messages { - height: 100%; - width: 100%; - padding: 10px; - font-size: 12px; - line-height: 1.3; -} - -.message-wrapper { - padding: 5px 0; - margin: 2px 0; - border-bottom: 1px dashed rgba(0, 0, 0, 0.25); -} - -.message-wrapper:last-of-type { - border: 0; -} diff --git a/webclient/src/containers/Room/Messages.tsx b/webclient/src/containers/Room/Messages.tsx deleted file mode 100644 index 24b576759..000000000 --- a/webclient/src/containers/Room/Messages.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line -import React from "react"; - -import { Message } from 'components'; - -import './Messages.css'; - -const Messages = ({ messages }) => ( -
- { - messages && messages.map((message, index) => ( -
- -
- )) - } -
-); - -export default Messages; diff --git a/webclient/src/containers/Room/OpenGames.css b/webclient/src/containers/Room/OpenGames.css deleted file mode 100644 index 623ab47f5..000000000 --- a/webclient/src/containers/Room/OpenGames.css +++ /dev/null @@ -1,30 +0,0 @@ -.games { -} - -.games-header, -.game { - display: flex; - padding: 10px; - border-bottom: 1px solid black; -} - -.games-header__cell { - max-width: 200px; -} - -.games-header__label, -.game__detail { - width: 10%; - flex-grow: 0; -} - -.games-header__label.description, -.game__detail.description { - width: 20%; - flex-grow: 1; -} - -.games-header__label.creator, -.game__detail.creator { - width: 20%; -} diff --git a/webclient/src/containers/Room/OpenGames.tsx b/webclient/src/containers/Room/OpenGames.tsx deleted file mode 100644 index 49d4d2503..000000000 --- a/webclient/src/containers/Room/OpenGames.tsx +++ /dev/null @@ -1,143 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import * as _ from 'lodash'; - -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import Tooltip from '@mui/material/Tooltip'; - -// import { RoomsService } from "AppShell/common/services"; - -import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; -import { UserDisplay } from 'components'; - -import './OpenGames.css'; - -// @TODO run interval to update timeSinceCreated -class OpenGames extends Component { - private headerCells = [ - { - label: 'Age', - field: 'startTime' - }, - { - label: 'Description', - field: 'description' - }, - { - label: 'Creator', - field: 'creatorInfo.name' - }, - { - label: 'Type', - field: 'gameType' - }, - { - label: 'Restrictions', - // field: "?" - }, - { - label: 'Players', - // field: ["maxPlayers", "playerCount"] - }, - { - label: 'Spectators', - field: 'spectatorsCount' - }, - ]; - - handleSort(sortByField) { - const { room: { roomId }, sortBy } = this.props; - const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); - RoomsDispatch.sortGames(roomId, field, order); - } - - private isUnavailableGame({ started, maxPlayers, playerCount }) { - return !started && playerCount < maxPlayers; - } - - private isPasswordProtectedGame({ withPassword }) { - return !withPassword; - } - - private isBuddiesOnlyGame({ onlyBuddies }) { - return !onlyBuddies; - } - - render() { - const { room, sortBy } = this.props; - - const games = room.gameList.filter(game => ( - this.isUnavailableGame(game) && - this.isPasswordProtectedGame(game) && - this.isBuddiesOnlyGame(game) - )); - - return ( -
- - - - { _.map(this.headerCells, ({ label, field }) => { - const active = field === sortBy.field; - const order = sortBy.order.toLowerCase(); - const sortDirection = active ? order : false; - - return ( - - {!field ? label : ( - this.handleSort(field)} - > - {label} - - )} - - ); - })} - - - - { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( - - {startTime} - - -
- {description} -
-
-
- - - - {gameType} - ? - {`${playerCount}/${maxPlayers}`} - {spectatorsCount} -
- ))} -
-
-
- ); - } -} - -interface OpenGamesProps { - room: any; - sortBy: any; -} - -const mapStateToProps = state => ({ - sortBy: RoomsSelectors.getSortGamesBy(state) -}); - -export default connect(mapStateToProps)(OpenGames); diff --git a/webclient/src/containers/Room/Room.css b/webclient/src/containers/Room/Room.css deleted file mode 100644 index 4b6aaec93..000000000 --- a/webclient/src/containers/Room/Room.css +++ /dev/null @@ -1,45 +0,0 @@ -.room-view, -.room-view__main, -.room-view__games, -.room-view__messages, -.room-view__messages-content, -.room-view__side { - height: 100%; -} - -.room-view, -.room-view__messages, -.room-view__side { - display: flex; - flex-direction: column; -} - -.room-view__main { - overflow: hidden; -} - -.room-view__messages-sayMessage { - width: 100%; - margin: 10px auto 2px; -} - -.room-view__side-label { - position: sticky; - top: 0; - padding: 10px; - background: white; - z-index: 1; -} - -.room-view__side-list, -.room-view__side-list .room-view__side-list__item { - height: 100%; -} - -.room-view__side-list .room-view__side-list__item { - padding: 0; -} - -.room-view__side-list .room-view__side-list__item .user-display__details { - padding: 0 10px; -} \ No newline at end of file diff --git a/webclient/src/containers/Room/Room.tsx b/webclient/src/containers/Room/Room.tsx deleted file mode 100644 index c73930d7f..000000000 --- a/webclient/src/containers/Room/Room.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; -import { useNavigate, useParams, generatePath } from 'react-router-dom'; - -import ListItem from '@mui/material/ListItem'; -import Paper from '@mui/material/Paper'; - -import { RoomsService } from 'api'; -import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from 'components'; -import { RoomsStateMessages, RoomsStateRooms, JoinedRooms, RoomsSelectors, RoomsTypes } from 'store'; -import { RouteEnum } from 'types'; -import Layout from 'containers/Layout/Layout'; - -import OpenGames from './OpenGames'; -import Messages from './Messages'; -import SayMessage from './SayMessage'; - -import './Room.css'; - -// @TODO (3) -const Room = (props) => { - const { joined, rooms, messages } = props; - const navigate = useNavigate(); - const params = useParams(); - - const roomId = parseInt(params.roomId, 0); - const room = rooms[roomId]; - const roomMessages = messages[roomId]; - const users = room.userList; - - useEffect(() => { - if (!joined.find(({ roomId: id }) => id === roomId)) { - navigate(generatePath(RouteEnum.SERVER)); - } - }, [joined]); - - const handleRoomSay = ({ message }) => { - if (message) { - RoomsService.roomSay(roomId, message); - } - } - - return ( - - - -
- - - - )} - - bottom={( -
- - - )} /> - - - - -
- )} - - side={( - -
- Users in this room: {users.length} -
- users[index].name } - items={ users.map(user => ( - - - - )) } - /> -
- )} - /> -
-
- ); -} - -interface RoomProps { - messages: RoomsStateMessages; - rooms: RoomsStateRooms; - joined: JoinedRooms; -} - -const mapStateToProps = state => ({ - messages: RoomsSelectors.getMessages(state), - rooms: RoomsSelectors.getRooms(state), - joined: RoomsSelectors.getJoinedRooms(state), -}); - -export default connect(mapStateToProps)(Room); diff --git a/webclient/src/containers/Room/SayMessage.tsx b/webclient/src/containers/Room/SayMessage.tsx deleted file mode 100644 index 4dd62dfb4..000000000 --- a/webclient/src/containers/Room/SayMessage.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { Form } from 'react-final-form' - -import { InputAction } from 'components'; - -const SayMessage = ({ onSubmit }) => ( -
- {({ handleSubmit, form }) => ( - { - handleSubmit(e) - form.restart() - }}> - - - )} - -); - -export default SayMessage; diff --git a/webclient/src/containers/Server/Rooms.css b/webclient/src/containers/Server/Rooms.css deleted file mode 100644 index bfcdc82cf..000000000 --- a/webclient/src/containers/Server/Rooms.css +++ /dev/null @@ -1,26 +0,0 @@ -.rooms { -} - -.rooms-header, -.room { - display: flex; - padding: 10px; - border-bottom: 1px solid black; -} - -.rooms-header__label, -.room__detail { - width: 10%; - flex-grow: 0; -} - -.rooms-header__label.name, -.room__detail.name { - width: 20%; -} - -.rooms-header__label.description, -.room__detail.description { - width: 30%; - flex-grow: 1; -} \ No newline at end of file diff --git a/webclient/src/containers/Server/Rooms.tsx b/webclient/src/containers/Server/Rooms.tsx deleted file mode 100644 index 517f24380..000000000 --- a/webclient/src/containers/Server/Rooms.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// eslint-disable-next-line -import React from "react"; -import { generatePath, useNavigate } from 'react-router-dom'; -import * as _ from 'lodash'; - -import Button from '@mui/material/Button'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; - - -import { RoomsService } from 'api'; -import { RouteEnum } from 'types'; - -import './Rooms.css'; - -const Rooms = ({ rooms, joinedRooms }) => { - const navigate = useNavigate(); - - function onClick(roomId) { - if (_.find(joinedRooms, room => room.roomId === roomId)) { - navigate(generatePath(RouteEnum.ROOM, { roomId })); - } else { - RoomsService.joinRoom(roomId); - } - } - - return ( -
- - - - Name - Description - Permissions - Players - Games - - - - - { _.map(rooms, ({ description, gameCount, name, permissionlevel, playerCount, roomId }) => ( - - {name} - {description} - {permissionlevel} - {playerCount} - {gameCount} - - - - - ))} - -
-
- ); -}; - -export default Rooms; diff --git a/webclient/src/containers/Server/Server.css b/webclient/src/containers/Server/Server.css deleted file mode 100644 index 8d4e2e8dd..000000000 --- a/webclient/src/containers/Server/Server.css +++ /dev/null @@ -1,34 +0,0 @@ -.server, -.server-rooms, -.server-rooms__side { - height: 100%; -} - -.server { - display: flex; - flex-direction: column; - align-items: center; -} - - -.serverRoomWrapper { - height: 100%; -} - -.serverMessage { - height: 100%; - padding: 20px; - margin-bottom: 2px; -} - -.server-rooms { - width: 100%; -} - -.server-rooms__side-label { - position: sticky; - top: 0; - padding: 10px; - background: white; - z-index: 1; -} diff --git a/webclient/src/containers/Server/Server.tsx b/webclient/src/containers/Server/Server.tsx deleted file mode 100644 index 7ed5e2464..000000000 --- a/webclient/src/containers/Server/Server.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import { generatePath, useNavigate } from 'react-router-dom'; - -import ListItem from '@mui/material/ListItem'; -import Paper from '@mui/material/Paper'; - -import { AuthGuard, ThreePaneLayout, UserDisplay, VirtualList } from 'components'; -import { useReduxEffect } from 'hooks'; -import { RoomsSelectors, RoomsTypes, ServerSelectors } from 'store'; -import { Room, RouteEnum, User } from 'types'; -import Rooms from './Rooms'; -import Layout from 'containers/Layout/Layout'; - -import './Server.css'; - -const Server = ({ message, rooms, joinedRooms, users }: ServerProps) => { - const navigate = useNavigate(); - - useReduxEffect((action: any) => { - const roomId = action.roomInfo.roomId.toString(); - navigate(generatePath(RouteEnum.ROOM, { roomId })); - }, RoomsTypes.JOIN_ROOM, []); - - return ( - - - - - - - )} - - bottom={( - -
- - )} - - side={( - -
- Users connected to server: {users.length} -
- users[index].name } - items={ users.map(user => ( - - - - )) } - /> -
- )} - /> - - ); -} - -interface ServerProps { - message: string; - rooms: Room[]; - joinedRooms: Room[]; - users: User[]; -} - -const mapStateToProps = state => ({ - message: ServerSelectors.getMessage(state), - rooms: RoomsSelectors.getRooms(state), - joinedRooms: RoomsSelectors.getJoinedRooms(state), - users: ServerSelectors.getUsers(state) -}); - -export default connect(mapStateToProps)(Server); diff --git a/webclient/src/containers/Unsupported/Unsupported.css b/webclient/src/containers/Unsupported/Unsupported.css deleted file mode 100644 index 46752e746..000000000 --- a/webclient/src/containers/Unsupported/Unsupported.css +++ /dev/null @@ -1,18 +0,0 @@ -.Unsupported { - height: 100%; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; -} - -.Unsupported-paper { - width: 600px; - max-width: 100%; - padding: 40px; - text-align: center; -} - -.Unsupported-paper__header { - margin-bottom: 40px; -} diff --git a/webclient/src/containers/Unsupported/Unsupported.i18n.json b/webclient/src/containers/Unsupported/Unsupported.i18n.json deleted file mode 100644 index 225a13a67..000000000 --- a/webclient/src/containers/Unsupported/Unsupported.i18n.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "UnsupportedContainer": { - "title": "Unsupported Browser", - "subtitle1": "Please update your browser and/or check your permissions.", - "subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features." - } -} diff --git a/webclient/src/containers/Unsupported/Unsupported.tsx b/webclient/src/containers/Unsupported/Unsupported.tsx deleted file mode 100644 index b666da12e..000000000 --- a/webclient/src/containers/Unsupported/Unsupported.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import Layout from 'containers/Layout/Layout'; - -import './Unsupported.css'; - -const Unsupported = () => { - const { t } = useTranslation(); - - return ( - - -
- { t('UnsupportedContainer.title') } - { t('UnsupportedContainer.subtitle1') } -
- - { t('UnsupportedContainer.subtitle2') } -
-
- ); -}; - -const mapStateToProps = state => ({ - -}); - -export default connect(mapStateToProps)(Unsupported); diff --git a/webclient/src/containers/index.ts b/webclient/src/containers/index.ts deleted file mode 100644 index f43de1c8e..000000000 --- a/webclient/src/containers/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { default as AppShell } from './App/AppShell'; -export { default as Account } from './Account/Account'; -export { default as Game } from './Game/Game'; -export { default as Decks } from './Decks/Decks'; -export { default as Room } from './Room/Room'; -export { default as Player } from './Player/Player'; -export { default as Server } from './Server/Server'; -export { default as Logs } from './Logs/Logs'; -export { default as Login } from './Login/Login'; -export { default as Initialize } from './Initialize/Initialize'; -export { default as Unsupported } from './Unsupported/Unsupported'; diff --git a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.css b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.css deleted file mode 100644 index 5175ab845..000000000 --- a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.css +++ /dev/null @@ -1,13 +0,0 @@ -.dialog-title { - display: flex; - justify-content: space-between; - align-items: center; -} - -.MuiDialogTitle-root.dialog-title { - padding-bottom: 0; -} - -.content { - margin-bottom: 20px; -} \ No newline at end of file diff --git a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.i18n.json b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.i18n.json deleted file mode 100644 index 9cfd876f5..000000000 --- a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.i18n.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "AccountActivationDialog": { - "title": "Account Activation", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - } -} diff --git a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx deleted file mode 100644 index cb5239619..000000000 --- a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -import { AccountActivationForm } from 'forms'; - -import './AccountActivationDialog.css'; - -const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => { - const { t } = useTranslation(); - - const handleOnClose = () => { - handleClose(); - } - - return ( - - - { t('AccountActivationDialog.title') } - - {handleOnClose ? ( - - - - ) : null} - - -
- { t('AccountActivationDialog.subtitle1') } - { t('AccountActivationDialog.subtitle2') } -
- - -
-
- ); -}; - -export default AccountActivationDialog; diff --git a/webclient/src/dialogs/CardImportDialog/CardImportDialog.css b/webclient/src/dialogs/CardImportDialog/CardImportDialog.css deleted file mode 100644 index b089ed200..000000000 --- a/webclient/src/dialogs/CardImportDialog/CardImportDialog.css +++ /dev/null @@ -1,5 +0,0 @@ -.dialog-title { - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx b/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx deleted file mode 100644 index 8011d317a..000000000 --- a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; - -import { CardImportForm } from 'forms'; - -import './CardImportDialog.css'; - -const CardImportDialog = ({ classes, handleClose, isOpen }: any) => { - const handleOnClose = () => { - handleClose(); - } - - return ( - - - Import Cards - - {handleOnClose ? ( - - - - ) : null} - - - - - - ); -}; - -export default CardImportDialog; diff --git a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.css b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.css deleted file mode 100644 index e8a350f0d..000000000 --- a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.css +++ /dev/null @@ -1,26 +0,0 @@ -.KnownHostDialog { - -} - -.KnownHostDialog .MuiDialog-paper { - width: 100%; - max-width: 420px; -} - -.dialog-title__wrapper { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid; - padding-bottom: 10px; -} - -.dialog-title__label { - display: flex; - align-items: center; -} - -.dialog-content__subtitle.MuiTypography-root { - margin-bottom: 20px; -} diff --git a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.i18n.json b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.i18n.json deleted file mode 100644 index 4ae521050..000000000 --- a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.i18n.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list." - } -} diff --git a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx deleted file mode 100644 index 5bde19d92..000000000 --- a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { styled } from '@mui/material/styles'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import AddIcon from '@mui/icons-material/Add'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -import { KnownHostForm } from 'forms'; - -import './KnownHostDialog.css'; - -const PREFIX = 'KnownHostDialog'; - -const classes = { - root: `${PREFIX}-root` -}; - -const StyledDialog = styled(Dialog)(({ theme }) => ({ - [`&.${classes.root}`]: { - '& .dialog-title__wrapper': { - borderColor: theme.palette.grey[300] - } - } -})); - -const KnownHostDialog = ({ handleClose, onRemove, onSubmit, isOpen, host }: any) => { - const { t } = useTranslation(); - - const mode = host ? 'edit' : 'add'; - - const handleOnClose = () => { - if (handleClose) { - handleClose(); - } - }; - - return ( - - -
- { t('KnownHostDialog.title', { mode }) } - - {handleClose ? ( - - - - ) : null} -
-
- - - { t('KnownHostDialog.subtitle') } - - - -
- ); -}; - -export default KnownHostDialog; diff --git a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.css b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.css deleted file mode 100644 index 73822f190..000000000 --- a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.css +++ /dev/null @@ -1,10 +0,0 @@ -.dialog-title { - display: flex; - justify-content: space-between; - align-items: center; -} - -.dialog-content { - width: 700px; - max-width: 100%; -} diff --git a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.i18n.json b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.i18n.json deleted file mode 100644 index bc0aa646d..000000000 --- a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.i18n.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "RegistrationDialog": { - "title": "Create New Account" - } -} diff --git a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx deleted file mode 100644 index 2388dddb3..000000000 --- a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -import { RegisterForm } from 'forms'; - -import './RegistrationDialog.css'; - -const RegistrationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => { - const { t } = useTranslation(); - - const handleOnClose = () => { - handleClose(); - } - - return ( - - - { t('RegistrationDialog.title') } - - {handleOnClose ? ( - - - - ) : null} - - - - - - ); -}; - -export default RegistrationDialog; diff --git a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.css b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.css deleted file mode 100644 index 731927c13..000000000 --- a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.css +++ /dev/null @@ -1,5 +0,0 @@ -.dialog-title { - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.i18n.json b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.i18n.json deleted file mode 100644 index c287b7a82..000000000 --- a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.i18n.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - } -} diff --git a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx deleted file mode 100644 index be2032a08..000000000 --- a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -import { RequestPasswordResetForm } from 'forms'; - -import './RequestPasswordResetDialog.css'; - -const RequestPasswordResetDialog = ({ classes, handleClose, isOpen, onSubmit, skipTokenRequest }: any) => { - const { t } = useTranslation(); - - const handleOnClose = () => { - handleClose(); - } - - return ( - - - { t('RequestPasswordResetDialog.title') } - - {handleOnClose ? ( - - - - ) : null} - - - - - - ); -}; - -export default RequestPasswordResetDialog; diff --git a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.css b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.css deleted file mode 100644 index 731927c13..000000000 --- a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.css +++ /dev/null @@ -1,5 +0,0 @@ -.dialog-title { - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.i18n.json b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.i18n.json deleted file mode 100644 index 8047dcae0..000000000 --- a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.i18n.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ResetPasswordDialog": { - "title": "Reset Password" - } -} diff --git a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx deleted file mode 100644 index 877c8729f..000000000 --- a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -import { ResetPasswordForm } from 'forms'; - -import './ResetPasswordDialog.css'; - -const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit, userName }: any) => { - const { t } = useTranslation(); - - const handleOnClose = () => { - handleClose(); - } - - return ( - - - {t('ResetPasswordDialog.title')} - - {handleOnClose ? ( - - - - ) : null} - - - - - - ); -}; - -export default ResetPasswordDialog; diff --git a/webclient/src/dialogs/index.ts b/webclient/src/dialogs/index.ts deleted file mode 100644 index 5c5ace1c5..000000000 --- a/webclient/src/dialogs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as AccountActivationDialog } from './AccountActivationDialog/AccountActivationDialog'; -export { default as CardImportDialog } from './CardImportDialog/CardImportDialog'; -export { default as KnownHostDialog } from './KnownHostDialog/KnownHostDialog'; -export { default as RegistrationDialog } from './RegistrationDialog/RegistrationDialog'; -export { default as RequestPasswordResetDialog } from './RequestPasswordResetDialog/RequestPasswordResetDialog'; -export { default as ResetPasswordDialog } from './ResetPasswordDialog/ResetPasswordDialog'; diff --git a/webclient/src/forms/AccountActivationForm/AccountActivationForm.css b/webclient/src/forms/AccountActivationForm/AccountActivationForm.css deleted file mode 100644 index ffb4ecc77..000000000 --- a/webclient/src/forms/AccountActivationForm/AccountActivationForm.css +++ /dev/null @@ -1,12 +0,0 @@ -.AccountActivationForm { - width: 100%; - padding-bottom: 15px; -} - -.AccountActivationForm-item { - margin-bottom: 20px; -} - -.AccountActivationForm-submit { - width: 100%; -} diff --git a/webclient/src/forms/AccountActivationForm/AccountActivationForm.i18n.json b/webclient/src/forms/AccountActivationForm/AccountActivationForm.i18n.json deleted file mode 100644 index 0b510fb83..000000000 --- a/webclient/src/forms/AccountActivationForm/AccountActivationForm.i18n.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "AccountActivationForm": { - "error": { - "failed": "Account activation failed" - }, - "label": { - "activate": "Activate Account" - } - } -} diff --git a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx b/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx deleted file mode 100644 index 61b20f9a5..000000000 --- a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// eslint-disable-next-line -import React, { useState } from "react"; -import { connect } from 'react-redux'; -import { Form, Field } from 'react-final-form'; -import { OnChange } from 'react-final-form-listeners'; -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; - -import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './AccountActivationForm.css'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; - -const AccountActivationForm = ({ onSubmit }) => { - const [errorMessage, setErrorMessage] = useState(false); - const { t } = useTranslation(); - - useReduxEffect(() => { - setErrorMessage(true); - }, ServerTypes.ACCOUNT_ACTIVATION_FAILED, []); - - const handleOnSubmit = ({ token, ...values }) => { - setErrorMessage(false); - - token = token?.trim(); - - onSubmit({ token, ...values }); - } - - const validate = values => { - const errors: any = {}; - - if (!values.token) { - errors.token = t('Common.validation.required'); - } - - return errors; - }; - - return ( -
- {({ handleSubmit, form }) => { - return ( - -
- -
- - {errorMessage && ( -
- { t('AccountActivationForm.error.failed') } -
- )} - - -
- ); - }} - - ); -}; - -export default AccountActivationForm; diff --git a/webclient/src/forms/CardImportForm/CardImportForm.css b/webclient/src/forms/CardImportForm/CardImportForm.css deleted file mode 100644 index 56ac3e16e..000000000 --- a/webclient/src/forms/CardImportForm/CardImportForm.css +++ /dev/null @@ -1,35 +0,0 @@ -.cardImportForm { - width: 550px; -} - -.cardImportForm-content.done { - font-size: 32px; - height: 150px; - display: flex; - align-items: center; - justify-content: center; -} - -.cardImportForm-actions { - display: flex; - justify-content: flex-end; - margin-top: 20px; -} - -.cardImportForm-error { - color: red; -} - -.card-import-list { - height: 300px; - line-height: 1; - border: 1px solid lightgrey; - padding: 10px; -} - -.loading { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} diff --git a/webclient/src/forms/CardImportForm/CardImportForm.tsx b/webclient/src/forms/CardImportForm/CardImportForm.tsx deleted file mode 100644 index b9745f31e..000000000 --- a/webclient/src/forms/CardImportForm/CardImportForm.tsx +++ /dev/null @@ -1,227 +0,0 @@ -// eslint-disable-next-line -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Form, Field, reduxForm } from 'redux-form' - -import Button from '@mui/material/Button'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import CircularProgress from '@mui/material/CircularProgress'; - -import { InputField, VirtualList } from 'components'; -import { cardImporterService, CardDTO, SetDTO, TokenDTO } from 'services'; -import { FormKey } from 'types'; - -import './CardImportForm.css'; - -const CardImportForm = (props) => { - const { handleSubmit, onSubmit: onClose } = props; - - const [loading, setLoading] = useState(false); - const [activeStep, setActiveStep] = useState(0); - const [importedCards, setImportedCards] = useState([]); - const [importedSets, setImportedSets] = useState([]); - const [error, setError] = useState(null); - - useEffect(() => { - if (loading) { - setError(null); - } - }, [loading]) - - const steps = ['Imports sets', 'Save sets', 'Import tokens', 'Finished']; - - const handleNext = () => { - setActiveStep((prevActiveStep) => prevActiveStep + 1); - }; - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - const handleCardDownload = ({ cardDownloadUrl }) => { - setLoading(true); - - cardImporterService.importCards(cardDownloadUrl) - .then(({ cards, sets }) => { - setImportedCards(cards); - setImportedSets(sets); - - handleNext(); - }) - .catch(({ message }) => setError(message)) - .finally(() => setLoading(false)); - }; - - const handleCardSave = async () => { - setLoading(true); - - try { - await CardDTO.bulkAdd(importedCards); - await SetDTO.bulkAdd(importedSets); - - handleNext(); - } catch (e) { - console.error(e); - setError('Failed to save cards'); - } - - setLoading(false); - }; - - const handleTokenDownload = ({ tokenDownloadUrl }) => { - setLoading(true); - - cardImporterService.importTokens(tokenDownloadUrl) - .then(async tokens => { - await TokenDTO.bulkAdd(tokens); - handleNext(); - }) - .catch(({ message }) => setError(message)) - .finally(() => setLoading(false)); - }; - - const getStepContent = (stepIndex) => { - switch (stepIndex) { - case 0: return ( -
-
- -
- -
- -
- -
- -
-
- ); - - case 1: return ( -
-
- -
- -
- - -
- -
- -
-
- ); - - case 2: return ( -
-
- -
- -
- - -
- -
- -
-
- ); - - case 3: return ( -
-
Finished!
- -
- - -
-
- ); - } - }; - - return ( -
- - {steps.map((label) => ( - - {label} - - ))} - - -
- { getStepContent(activeStep) } -
- - { loading && ( -
- -
- ) } -
- ); -}; - -const BackButton = ({ click, disabled }) => ( - -); - -const ErrorMessage = ({ error }) => { - return error && ( -
{error}
- ); -}; - -const CardsImported = ({ cards, sets }) => { - const items = [ - ( -
- Import finished: {cards.length} cards. -
- ), - - (
), - - ...sets.map(set => ( -
{set.name}: {set.cards.length} cards imported
- )) - ]; - - return ( -
- index } - items={items} - size={15} - /> -
- ); -}; - -const propsMap = { - form: FormKey.CARD_IMPORT, - onClose: Function -}; - -const mapStateToProps = () => ({ - initialValues: { - cardDownloadUrl: 'https://www.mtgjson.com/api/v5/AllPrintings.json', - tokenDownloadUrl: 'https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml' - }, -}); - -export default connect(mapStateToProps)(reduxForm(propsMap)(CardImportForm)); diff --git a/webclient/src/forms/KnownHostForm/KnownHostForm.css b/webclient/src/forms/KnownHostForm/KnownHostForm.css deleted file mode 100644 index 76e1722e0..000000000 --- a/webclient/src/forms/KnownHostForm/KnownHostForm.css +++ /dev/null @@ -1,21 +0,0 @@ -.KnownHostForm { - width: 100%; -} - -.KnownHostForm-item { - display: flex; - flex-direction: column; - margin-bottom: 20px; -} - -.KnownHostForm-submit { - width: 100%; -} - -.KnownHostForm-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin: 5px 0 10px; - color: red; -} diff --git a/webclient/src/forms/KnownHostForm/KnownHostForm.i18n.json b/webclient/src/forms/KnownHostForm/KnownHostForm.i18n.json deleted file mode 100644 index fe614a126..000000000 --- a/webclient/src/forms/KnownHostForm/KnownHostForm.i18n.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "KnownHostForm": { - "help": "Need help adding a new host?", - "label": { - "add": "Add Host", - "find": "Find Host" - } - } -} diff --git a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx b/webclient/src/forms/KnownHostForm/KnownHostForm.tsx deleted file mode 100644 index 1241336f3..000000000 --- a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// eslint-disable-next-line -import React, { useState } from "react"; -import { connect } from 'react-redux'; -import { Form, Field } from 'react-final-form' -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; -import AnchorLink from '@mui/material/Link'; - -import { InputField } from 'components'; - -import './KnownHostForm.css'; - -const KnownHostForm = ({ host, onRemove, onSubmit }) => { - const [confirmDelete, setConfirmDelete] = useState(false); - const { t } = useTranslation(); - - const validate = values => { - const errors: any = {}; - - if (!values.name) { - errors.name = t('Common.validation.required'); - } - - if (!values.host) { - errors.host = t('Common.validation.required'); - } - - if (!values.port) { - errors.port = t('Common.validation.required'); - } - - if (Object.keys(errors).length) { - return errors; - } - }; - - const handleOnSubmit = ({ name, host, ...values }) => { - name = name?.trim(); - host = host?.trim(); - - onSubmit({ name, host, ...values }); - } - - return ( -
- {({ handleSubmit }) => ( - -
- -
-
- -
-
- -
- - - -
-
- { host && ( - - ) } -
- - { t('KnownHostForm.label.find') } - -
-
- ) } - - ); -}; - -const mapStateToProps = () => ({ - -}); - -export default connect(mapStateToProps)(KnownHostForm); diff --git a/webclient/src/forms/LoginForm/LoginForm.css b/webclient/src/forms/LoginForm/LoginForm.css deleted file mode 100644 index 4b176476f..000000000 --- a/webclient/src/forms/LoginForm/LoginForm.css +++ /dev/null @@ -1,20 +0,0 @@ -.loginForm { - width: 100%; -} - -.loginForm-item { - margin-bottom: 20px; -} - -.loginForm-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: -20px; - margin-bottom: 20px; - font-weight: bold; -} - -.loginForm-submit { - width: 100%; -} diff --git a/webclient/src/forms/LoginForm/LoginForm.i18n.json b/webclient/src/forms/LoginForm/LoginForm.i18n.json deleted file mode 100644 index 260b88b27..000000000 --- a/webclient/src/forms/LoginForm/LoginForm.i18n.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "Forgot Password", - "login": "Login", - "savePassword": "Save Password", - "savedPassword": "Saved Password" - } - } -} diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx deleted file mode 100644 index 1e8f104fe..000000000 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react'; -import { Form, Field } from 'react-final-form'; -import { OnChange } from 'react-final-form-listeners'; -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; - -import { AuthenticationService } from 'api'; -import { CheckboxField, InputField, KnownHosts } from 'components'; -import { useAutoConnect } from 'hooks'; -import { HostDTO, SettingDTO } from 'services'; -import { APP_USER } from 'types'; - -import './LoginForm.css'; - -const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginFormProps) => { - const { t } = useTranslation(); - const PASSWORD_LABEL = t('Common.label.password'); - const STORED_PASSWORD_LABEL = `* ${t('LoginForm.label.savedPassword')} *`; - - const [host, setHost] = useState(null); - const [useStoredPasswordLabel, setUseStoredPasswordLabel] = useState(false); - const [autoConnect, setAutoConnect] = useAutoConnect(); - - const validate = values => { - const errors: any = {}; - - if (!values.userName) { - errors.userName = t('Common.validation.required'); - } - if (!values.selectedHost) { - errors.selectedHost = t('Common.validation.required'); - } - - return errors; - } - - const useStoredPassword = (remember, password) => remember && host?.hashedPassword && !password; - const togglePasswordLabel = (useStoredLabel) => { - setUseStoredPasswordLabel(useStoredLabel); - }; - - const handleOnSubmit = ({ userName, ...values }) => { - userName = userName?.trim(); - console.log(userName, values); - - onSubmit({ userName, ...values }); - } - - return ( -
- {({ handleSubmit, form }) => { - const { values } = form.getState(); - - useEffect(() => { - SettingDTO.get(APP_USER).then((userSetting: SettingDTO) => { - if (userSetting?.autoConnect && !AuthenticationService.connectionAttemptMade()) { - HostDTO.getAll().then(hosts => { - let lastSelectedHost = hosts.find(({ lastSelected }) => lastSelected); - - if (lastSelectedHost?.remember && lastSelectedHost?.hashedPassword) { - togglePasswordLabel(true); - - form.change('selectedHost', lastSelectedHost); - form.change('userName', lastSelectedHost.userName); - form.change('remember', true); - form.submit(); - } - }); - } - }); - }, []); - - useEffect(() => { - if (!host) { - return; - } - - form.change('userName', host.userName); - form.change('password', ''); - - onRememberChange(host.remember); - onAutoConnectChange(host.remember && autoConnect); - togglePasswordLabel(useStoredPassword(host.remember, values.password)); - }, [host]); - - const onUserNameChange = (userName) => { - const fieldChanged = host?.userName?.toLowerCase() !== values.userName?.toLowerCase(); - if (useStoredPassword(values.remember, values.password) && fieldChanged) { - setHost(({ hashedPassword, ...s }) => ({ ...s, userName })); - } - } - - const onRememberChange = (checked) => { - form.change('remember', checked); - - if (!checked && values.autoConnect) { - onAutoConnectChange(false); - } - - togglePasswordLabel(useStoredPassword(checked, values.password)); - } - - const onAutoConnectChange = (checked) => { - setAutoConnect(checked); - - form.change('autoConnect', checked); - - if (checked && !values.remember) { - form.change('remember', true); - } - } - - return ( - -
-
- - {onUserNameChange} -
-
- setUseStoredPasswordLabel(false)} - onBlur={() => togglePasswordLabel(useStoredPassword(values.remember, values.password))} - name='password' - type='password' - component={InputField} - autoComplete='new-password' - /> -
-
- - {onRememberChange} - - -
-
- - {setHost} -
-
- - {onAutoConnectChange} -
-
- -
- ) - }} - - ); -}; - -interface LoginFormProps { - onSubmit: any; - disableSubmitButton: boolean, - onResetPassword: any; -} - -export default LoginForm; diff --git a/webclient/src/forms/RegisterForm/RegisterForm.css b/webclient/src/forms/RegisterForm/RegisterForm.css deleted file mode 100644 index 324aa7e63..000000000 --- a/webclient/src/forms/RegisterForm/RegisterForm.css +++ /dev/null @@ -1,18 +0,0 @@ -.RegisterForm { - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.RegisterForm-column { - width: 48%; -} - -.RegisterForm-item { - margin-bottom: 20px; -} - -.RegisterForm-submit { - width: 100%; -} diff --git a/webclient/src/forms/RegisterForm/RegisterForm.i18n.json b/webclient/src/forms/RegisterForm/RegisterForm.i18n.json deleted file mode 100644 index 50e802ea5..000000000 --- a/webclient/src/forms/RegisterForm/RegisterForm.i18n.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - } -} diff --git a/webclient/src/forms/RegisterForm/RegisterForm.tsx b/webclient/src/forms/RegisterForm/RegisterForm.tsx deleted file mode 100644 index f5f4d9174..000000000 --- a/webclient/src/forms/RegisterForm/RegisterForm.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useState } from 'react'; -import { Form, Field } from 'react-final-form'; -import { OnChange } from 'react-final-form-listeners'; -import setFieldTouched from 'final-form-set-field-touched'; -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; - -import { CountryDropdown, InputField, KnownHosts } from 'components'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; - -import './RegisterForm.css'; -import { useToast } from 'components/Toast'; - -const RegisterForm = ({ onSubmit }: RegisterFormProps) => { - const { t } = useTranslation(); - const [emailRequired, setEmailRequired] = useState(false); - const [error, setError] = useState(null); - const [emailError, setEmailError] = useState(null); - const [passwordError, setPasswordError] = useState(null); - const [userNameError, setUserNameError] = useState(null); - const { openToast } = useToast({ key: 'registration-success', children: t('RegisterForm.toast.registerSuccess') }) - - const onHostChange = (host) => setEmailRequired(false); - const onEmailChange = () => emailError && setEmailError(null); - const onPasswordChange = () => passwordError && setPasswordError(null); - const onUserNameChange = () => userNameError && setUserNameError(null); - - useReduxEffect(() => { - setEmailRequired(true); - }, ServerTypes.REGISTRATION_REQUIRES_EMAIL); - - useReduxEffect(({ error }) => { - setError(error); - }, ServerTypes.REGISTRATION_FAILED); - - useReduxEffect(() => { - openToast() - }, ServerTypes.REGISTRATION_SUCCES); - - useReduxEffect(({ error }) => { - setEmailError(error); - }, ServerTypes.REGISTRATION_EMAIL_ERROR); - - useReduxEffect(({ error }) => { - setPasswordError(error); - }, ServerTypes.REGISTRATION_PASSWORD_ERROR); - - useReduxEffect(({ error }) => { - setUserNameError(error); - }, ServerTypes.REGISTRATION_USERNAME_ERROR); - - const handleOnSubmit = ({ userName, email, realName, ...values }) => { - setError(null); - - userName = userName?.trim(); - email = email?.trim(); - realName = realName?.trim(); - - onSubmit({ userName, email, realName, ...values }); - } - - const validate = values => { - const errors: any = {}; - - if (!values.userName) { - errors.userName = t('Common.validation.required'); - } else if (userNameError) { - errors.userName = userNameError; - } - - if (!values.password) { - errors.password = t('Common.validation.required'); - } else if (values.password.length < 8) { - errors.password = t('Common.validation.minChars', { count: 8 }); - } else if (passwordError) { - errors.password = passwordError; - } - - if (!values.passwordConfirm) { - errors.passwordConfirm = t('Common.validation.required'); - } else if (values.password !== values.passwordConfirm) { - errors.passwordConfirm = t('Common.validation.passwordsMustMatch'); - } - - if (!values.selectedHost) { - errors.selectedHost = t('Common.validation.required'); - } - - if (emailRequired && !values.email) { - errors.email = t('Common.validation.required'); - } else if (emailError) { - errors.email = emailError; - } - - return errors; - } - - return ( -
- {({ handleSubmit, form, ...args }) => { - const { values } = form.getState(); - - if (emailRequired) { - // Allow form render to complete - setTimeout(() => form.mutators.setFieldTouched('email', true)) - } - - return ( - <> - -
-
- - {onUserNameChange} -
-
- - {onPasswordChange} -
-
- -
-
- - {onHostChange} -
-
-
-
- -
-
- - {onEmailChange} -
-
- -
- -
-
- - { error && ( -
- {error} -
- )} - - ); - }} - - - ); -}; - -interface RegisterFormProps { - onSubmit: any; -} - -export default RegisterForm; diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css deleted file mode 100644 index 83ed7420f..000000000 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css +++ /dev/null @@ -1,25 +0,0 @@ -.RequestPasswordResetForm { - width: 100%; - padding-bottom: 15px; -} - -.RequestPasswordResetForm-item { - margin-bottom: 20px; -} - -.RequestPasswordResetForm-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: -20px; - margin-bottom: 20px; - font-weight: bold; -} - -.RequestPasswordResetForm-submit { - width: 100%; -} - -.selectedHost { - margin-top: 40px; -} diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.i18n.json b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.i18n.json deleted file mode 100644 index c53f6d097..000000000 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.i18n.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "RequestPasswordResetForm": { - "error": "Request password reset failed", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - } -} diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx deleted file mode 100644 index 01d4a2959..000000000 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// eslint-disable-next-line -import React, { useState } from "react"; -import { connect } from 'react-redux'; -import { Form, Field } from 'react-final-form'; -import { OnChange } from 'react-final-form-listeners'; -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; - -import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './RequestPasswordResetForm.css'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; - -const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => { - const [errorMessage, setErrorMessage] = useState(false); - const [isMFA, setIsMFA] = useState(false); - const { t } = useTranslation(); - - useReduxEffect(() => { - setErrorMessage(true); - }, ServerTypes.RESET_PASSWORD_FAILED, []); - - useReduxEffect(() => { - setIsMFA(true); - }, ServerTypes.RESET_PASSWORD_CHALLENGE, []); - - const handleOnSubmit = ({ userName, email, ...values }) => { - setErrorMessage(false); - - userName = userName?.trim(); - email = email?.trim(); - - onSubmit({ userName, email, ...values }); - } - - const validate = values => { - const errors: any = {}; - - if (!values.userName) { - errors.userName = t('Common.validation.required'); - } - if (isMFA && !values.email) { - errors.email = t('Common.validation.required'); - } - if (!values.selectedHost) { - errors.selectedHost = t('Common.validation.required'); - } - - return errors; - }; - - return ( -
- {({ handleSubmit, form }) => { - const onHostChange: any = ({ userName }) => { - form.change('userName', userName); - setIsMFA(false); - } - - return ( - -
-
- -
- {isMFA ? ( -
- -
{ t('RequestPasswordResetForm.mfaEnabled') }
-
- ) : null} -
- - {onHostChange} -
- - {errorMessage && ( -
- { t('RequestPasswordResetForm.error') } -
- )} -
- - - -
- -
-
- ); - }} - - ); -}; - -export default RequestPasswordResetForm; diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.css b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.css deleted file mode 100644 index ad82b7efd..000000000 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.css +++ /dev/null @@ -1,21 +0,0 @@ -.ResetPasswordForm { - width: 100%; - padding-bottom: 15px; -} - -.ResetPasswordForm-item { - margin-bottom: 20px; -} - -.ResetPasswordForm-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: -20px; - margin-bottom: 20px; - font-weight: bold; -} - -.ResetPasswordForm-submit { - width: 100%; -} diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.i18n.json b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.i18n.json deleted file mode 100644 index 2835134c0..000000000 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.i18n.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ResetPasswordForm": { - "error": "Password reset failed", - "label": { - "reset": "Reset Password" - } - } -} diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx deleted file mode 100644 index 8b56f4b69..000000000 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// eslint-disable-next-line -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Form, Field } from 'react-final-form' -import { OnChange } from 'react-final-form-listeners'; -import { useTranslation } from 'react-i18next'; - -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; - -import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './ResetPasswordForm.css'; -import { useReduxEffect } from '../../hooks'; -import { ServerTypes } from '../../store'; - -const ResetPasswordForm = ({ onSubmit, userName }) => { - const [errorMessage, setErrorMessage] = useState(false); - const { t } = useTranslation(); - - useReduxEffect(() => { - setErrorMessage(true); - }, ServerTypes.RESET_PASSWORD_FAILED, []); - - const validate = values => { - const errors: any = {}; - - if (!values.userName) { - errors.userName = t('Common.validation.required'); - } - if (!values.token) { - errors.token = t('Common.validation.required'); - } - - if (!values.newPassword) { - errors.newPassword = t('Common.validation.required'); - } else if (values.newPassword.length < 8) { - errors.newPassword = t('Common.validation.minChars', { count: 8 }); - } - - if (!values.passwordAgain) { - errors.passwordAgain = t('Common.validation.required'); - } else if (values.newPassword !== values.passwordAgain) { - errors.passwordAgain = t('Common.validation.passwordsMustMatch'); - } - if (!values.selectedHost) { - errors.selectedHost = t('Common.validation.required'); - } - - return errors; - }; - - const handleOnSubmit = ({ userName, token, ...values }) => { - userName = userName?.trim(); - token = token?.trim(); - - onSubmit({ userName, token, ...values }); - } - - return ( -
- {({ handleSubmit, form }) => ( - -
-
- -
-
- -
-
- -
-
- -
-
- -
- - {errorMessage && ( -
- { t('ResetPasswordForm.error') } -
- )} -
- -
- )} - - ); -}; - -export default ResetPasswordForm; diff --git a/webclient/src/forms/SearchForm/SearchForm.css b/webclient/src/forms/SearchForm/SearchForm.css deleted file mode 100644 index 9e3cd6cb8..000000000 --- a/webclient/src/forms/SearchForm/SearchForm.css +++ /dev/null @@ -1,35 +0,0 @@ -.log-search { - margin-bottom: 20px; -} - -hr.MuiDivider-root { - margin: 20px 0; -} - -.log-search__form { - width: 100%; - padding: 20px; -} - -.log-search__form-item { - display: flex; -} - -.log-search__form-item.log-location { - display: flex; - justify-content: space-around; -} - -.log-search__form-item.log-location .checkbox-field { - display: flex; - flex-direction: column; -} - -.log-search__form-item.log-location .checkbox-field__box { - order: 1; -} - -.log-search__form-submit.MuiButton-root { - display: block; - margin: 0 auto; -} diff --git a/webclient/src/forms/SearchForm/SearchForm.tsx b/webclient/src/forms/SearchForm/SearchForm.tsx deleted file mode 100644 index 793543086..000000000 --- a/webclient/src/forms/SearchForm/SearchForm.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import { Form, Field, reduxForm } from 'redux-form' - -import Button from '@mui/material/Button'; -import Divider from '@mui/material/Divider'; -import Paper from '@mui/material/Paper'; - -import { InputField, CheckboxField } from 'components'; -import { FormKey } from 'types'; - -import './SearchForm.css'; - -const SearchForm = ({ handleSubmit }) => ( - -
-
- -
-
- -
-
- -
-
- -
-
- -
- -
- - - -
- -
- Date Range: Coming Soon -
- -
- Maximum Results: 1000 -
- - - -
-); - -const propsMap = { - form: FormKey.SEARCH_LOGS, -}; - -const mapStateToProps = () => ({ - -}); - -export default connect(mapStateToProps)(reduxForm(propsMap)(SearchForm)); diff --git a/webclient/src/forms/index.ts b/webclient/src/forms/index.ts deleted file mode 100644 index b922a9f40..000000000 --- a/webclient/src/forms/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { default as AccountActivationForm } from './AccountActivationForm/AccountActivationForm'; -export { default as CardImportForm } from './CardImportForm/CardImportForm'; -export { default as LoginForm } from './LoginForm/LoginForm'; -export { default as KnownHostForm } from './KnownHostForm/KnownHostForm'; -export { default as RegisterForm } from './RegisterForm/RegisterForm'; -export { default as SearchForm } from './SearchForm/SearchForm'; -export { default as RequestPasswordResetForm } from './RequestPasswordResetForm/RequestPasswordResetForm'; -export { default as ResetPasswordForm } from './ResetPasswordForm/ResetPasswordForm'; diff --git a/webclient/src/hooks/index.ts b/webclient/src/hooks/index.ts deleted file mode 100644 index b5e9cca55..000000000 --- a/webclient/src/hooks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './useAutoConnect'; -export * from './useFireOnce'; -export * from './useDebounce'; -export * from './useLocaleSort'; -export * from './useReduxEffect'; diff --git a/webclient/src/hooks/useAutoConnect.ts b/webclient/src/hooks/useAutoConnect.ts deleted file mode 100644 index 5258f58a7..000000000 --- a/webclient/src/hooks/useAutoConnect.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useState } from 'react'; -import { debounce, DebouncedFunc } from 'lodash'; - -import { SettingDTO } from 'services'; -import { APP_USER } from 'types'; - -type OnChange = () => void; - -export function useAutoConnect() { - const [setting, setSetting] = useState(undefined); - const [autoConnect, setAutoConnect] = useState(undefined); - - useEffect(() => { - SettingDTO.get(APP_USER).then((setting: SettingDTO) => { - if (!setting) { - setting = new SettingDTO(APP_USER); - setting.save(); - } - - setSetting(setting); - }); - }, []); - - useEffect(() => { - if (setting) { - setAutoConnect(setting.autoConnect); - } - }, [setting]); - - useEffect(() => { - if (setting) { - setting.autoConnect = autoConnect; - setting.save(); - } - }, [setting, autoConnect]); - - return [autoConnect, setAutoConnect]; -} diff --git a/webclient/src/hooks/useDebounce.ts b/webclient/src/hooks/useDebounce.ts deleted file mode 100644 index 65aee9982..000000000 --- a/webclient/src/hooks/useDebounce.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useCallback } from 'react'; -import { debounce, DebouncedFunc } from 'lodash'; - -type UseDebounceType = (...args: any) => any; -const DEBOUNCE_DELAY = 250; - -export function useDebounce( - fn: T, - deps: any[] = [], - timeout: number = DEBOUNCE_DELAY -): DebouncedFunc { - return useCallback(debounce(fn, timeout), deps); -} diff --git a/webclient/src/hooks/useFireOnce/index.ts b/webclient/src/hooks/useFireOnce/index.ts deleted file mode 100644 index 442da884f..000000000 --- a/webclient/src/hooks/useFireOnce/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useFireOnce' diff --git a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx b/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx deleted file mode 100644 index 2f6ed34e0..000000000 --- a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { - render, - fireEvent, - getByRole, - waitFor, - act -} from '@testing-library/react'; -import { useFireOnce } from './useFireOnce'; - -describe('useFireOnce hook', () => { - test('it only fires once when button is clicked twice', async () => { - // Mock a promise with a delay - const onClickWithPromise = jest.fn((e) => { - e.preventDefault() - return new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, 100); - }); - }); - - function Button(props) { - const { children, onClick } = props - const [buttonIsDisabled, setButtonIsDisabled, handleClickOnce] = useFireOnce(onClick) - return - } - - // render the button - const { getByRole } = render( - - ); - - //Grab the button from the DOM and confirm it initialized in an enabled state - const button = getByRole('button', { name: 'Click Me!' }); - expect(button).toBeEnabled(); - - // Simulate two click events in a row - fireEvent.click(button); - fireEvent.click(button); - - // Confirm that it's disabled - await waitFor(() => { - expect(button).toBeDisabled(); - }); - - // Confirm it became enabled after the timeout and that the click event was only fired once - await waitFor( - () => { - expect(onClickWithPromise).toHaveBeenCalledTimes(1); - }, - { timeout: 100 } - ); - }); - - test('it only fires once when form is submitted twice', async () => { - // Mock a promise with a delay - const onClickWithPromise = jest.fn((e) => { - e.preventDefault() - return new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, 100); - }); - }); - - function Form(props) { - const { onSubmit } = props - const [buttonIsDisabled, setButtonIsDisabled, handleSubmitOnce] = useFireOnce(onSubmit) - return ( -
- - -
- ) - } - - // render the form - const { getByRole } = render( -
- ); - - //Grab the button from the DOM and confirm it initialized in an enabled state - const button = getByRole('button', { name: 'Click Me!' }); - expect(button).toBeEnabled(); - - // Simulate two click events in a row - fireEvent.click(button); - fireEvent.click(button); - - // Confirm that it's disabled - await waitFor(() => { - expect(button).toBeDisabled(); - }); - - // Confirm it became enabled after the timeout and that the click event was only fired once - await waitFor( - () => { - expect(onClickWithPromise).toHaveBeenCalledTimes(1); - }, - { timeout: 100 } - ); - }); -}); diff --git a/webclient/src/hooks/useFireOnce/useFireOnce.ts b/webclient/src/hooks/useFireOnce/useFireOnce.ts deleted file mode 100644 index 0b042a907..000000000 --- a/webclient/src/hooks/useFireOnce/useFireOnce.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback, useState } from 'react'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; - -type UseFireOnceType = (...args: any) => any; - -export function useFireOnce(fn: T): [boolean, any, any] { - const [actionIsInFlight, setActionIsInFlight] = useState(false) - const handleFireOnce = useCallback((args) => { - setActionIsInFlight(true); - fn(args); - }, []) - function resetInFlightStatus() { - setActionIsInFlight(false); - } - return [actionIsInFlight, resetInFlightStatus, handleFireOnce] -} diff --git a/webclient/src/hooks/useLocaleSort.ts b/webclient/src/hooks/useLocaleSort.ts deleted file mode 100644 index 219292ed7..000000000 --- a/webclient/src/hooks/useLocaleSort.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -export function useLocaleSort(arr: string[], valueGetter: (value: string) => string) { - const [state] = useState(arr); - const [sorted, setSorted] = useState([]); - - const { i18n } = useTranslation(); - - useEffect(() => { - const collator = new Intl.Collator(i18n.language); - const sorter = (a, b) => collator.compare(valueGetter(a), valueGetter(b)); - - setSorted(state.sort(sorter)); - }, [state, i18n.language]); - - return sorted; -} diff --git a/webclient/src/hooks/useReduxEffect.tsx b/webclient/src/hooks/useReduxEffect.tsx deleted file mode 100644 index 83273a6d3..000000000 --- a/webclient/src/hooks/useReduxEffect.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/** -File is adapted from https://github.com/Qeepsake/use-redux-effect under MIT License - * @author Aspect Apps Limited - * @description - */ - -import { useRef, useEffect, DependencyList } from 'react' -import { useStore } from 'react-redux' -import { AnyAction } from 'redux' -import { castArray } from 'lodash' - -export type ReduxEffect = (action: AnyAction) => void - -/** - * Subscribes to redux store events - * - * @param effect - * @param type - * @param deps - */ -export function useReduxEffect( - effect: ReduxEffect, - type: string | string[], - deps: DependencyList = [], -): void { - const currentValue = useRef(null); - const store = useStore(); - - const handleChange = (): void => { - const state: any = store.getState(); - const action = state.action; - const previousValue = currentValue.current; - currentValue.current = action.count; - - if ( - previousValue !== action.count && - castArray(type).includes(action.type) - ) { - effect(action); - } - } - - useEffect(() => { - const unsubscribe = store.subscribe(handleChange); - return (): void => unsubscribe(); - }, deps) -} diff --git a/webclient/src/i18n-backend.ts b/webclient/src/i18n-backend.ts deleted file mode 100644 index d270b3503..000000000 --- a/webclient/src/i18n-backend.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ModuleType } from 'i18next'; - -import { Language } from 'types'; - -class I18nBackend { - static type: ModuleType = 'backend'; - static BASE_URL = `${process.env.PUBLIC_URL}/locales`; - - read(language, namespace, callback) { - if (!Language[language]) { - callback(true, null); - return; - } - - fetch(`${I18nBackend.BASE_URL}/${Language[language]}/${namespace}.json`) - .then(resp => resp.json().then(json => callback(null, json))) - .catch(error => callback(error, null)); - } -} - -export default I18nBackend; diff --git a/webclient/src/i18n-default.json b/webclient/src/i18n-default.json deleted file mode 100644 index 3c63e0abb..000000000 --- a/webclient/src/i18n-default.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "Common": { - "language": "English", - "disconnect": "Disconnect", - "label": { - "confirmPassword": "Confirm Password", - "confirmSure": "Are you sure?", - "country": "Country", - "delete": "Delete", - "email": "Email", - "hostName": "Host Name", - "hostAddress": "Host Address", - "password": "Password", - "passwordAgain": "Password Again", - "port": "Port", - "realName": "Real Name", - "saveChanges": "Save Changes", - "token": "Token", - "username": "Username" - }, - "validation": { - "minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required", - "passwordsMustMatch": "Passwords don't match", - "required": "Required" - }, - "countries": { - "AD": "Andorra", - "AE": "United Arab Emirates", - "AF": "Afghanistan", - "AG": "Antigua and Barbuda", - "AI": "Anguilla", - "AL": "Albania", - "AM": "Armenia", - "AO": "Angola", - "AQ": "Antarctica", - "AR": "Argentina", - "AS": "American Samoa", - "AT": "Austria", - "AU": "Australia", - "AW": "Aruba", - "AX": "Åland Islands", - "AZ": "Azerbaijan", - "BA": "Bosnia and Herzegovina", - "BB": "Barbados", - "BD": "Bangladesh", - "BE": "Belgium", - "BF": "Burkina Faso", - "BG": "Bulgaria", - "BH": "Bahrain", - "BI": "Burundi", - "BJ": "Benin", - "BL": "Saint Barthélemy", - "BM": "Bermuda", - "BN": "Brunei Darussalam", - "BO": "Bolivia", - "BQ": "Bonaire, Sint Eustatius and Saba", - "BR": "Brazil", - "BS": "Bahamas", - "BT": "Bhutan", - "BV": "Bouvet Island", - "BW": "Botswana", - "BY": "Belarus", - "BZ": "Belize", - "CA": "Canada", - "CC": "Cocos (Keeling) Islands", - "CD": "DR Congo", - "CF": "Central African Republic", - "CG": "Republic of the Congo", - "CH": "Switzerland", - "CI": "Ivory Coast", - "CK": "Cook Islands", - "CL": "Chile", - "CM": "Cameroon", - "CN": "China", - "CO": "Colombia", - "CR": "Costa Rica", - "CU": "Cuba", - "CV": "Cape Verde", - "CW": "Curaçao", - "CX": "Christmas Island", - "CY": "Cyprus", - "CZ": "Czechia", - "DE": "Germany", - "DJ": "Djibouti", - "DK": "Denmark", - "DM": "Dominica", - "DO": "Dominican Republic", - "DZ": "Algeria", - "EC": "Ecuador", - "EE": "Estonia", - "EG": "Egypt", - "EH": "Western Sahara", - "ER": "Eritrea", - "ES": "Spain", - "ET": "Ethiopia", - "FI": "Finland", - "FJ": "Fiji", - "FK": "Falkland Islands", - "FM": "Micronesia", - "FO": "Faroe Islands", - "FR": "France", - "GA": "Gabon", - "GB": "United Kingdom", - "GD": "Grenada", - "GE": "Georgia", - "GF": "French Guiana", - "GG": "Guernsey", - "GH": "Ghana", - "GI": "Gibraltar", - "GL": "Greenland", - "GM": "Gambia", - "GN": "Guinea", - "GP": "Guadeloupe", - "GQ": "Equatorial Guinea", - "GR": "Greece", - "GS": "South Georgia and the South Sandwich Islands", - "GT": "Guatemala", - "GU": "Guam", - "GW": "Guinea-Bissau", - "GY": "Guyana", - "HK": "Hong Kong", - "HM": "Heard Island and McDonald Islands", - "HN": "Honduras", - "HR": "Croatia", - "HT": "Haiti", - "HU": "Hungary", - "ID": "Indonesia", - "IE": "Ireland", - "IL": "Israel", - "IM": "Isle of Man", - "IN": "India", - "IO": "British Indian Ocean Territory", - "IQ": "Iraq", - "IR": "Iran", - "IS": "Iceland", - "IT": "Italy", - "JE": "Jersey", - "JM": "Jamaica", - "JO": "Jordan", - "JP": "Japan", - "KE": "Kenya", - "KG": "Kyrgyzstan", - "KH": "Cambodia", - "KI": "Kiribati", - "KM": "Comoros", - "KN": "Saint Kitts and Nevis", - "KP": "North Korea", - "KR": "South Korea", - "KW": "Kuwait", - "KY": "Cayman Islands", - "KZ": "Kazakhstan", - "LA": "Laos", - "LB": "Lebanon", - "LC": "Saint Lucia", - "LI": "Liechtenstein", - "LK": "Sri Lanka", - "LR": "Liberia", - "LS": "Lesotho", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "LY": "Libya", - "MA": "Morocco", - "MC": "Monaco", - "MD": "Moldova", - "ME": "Montenegro", - "MF": "Saint Martin (French part)", - "MG": "Madagascar", - "MH": "Marshall Islands", - "MK": "North Macedonia", - "ML": "Mali", - "MM": "Myanmar", - "MN": "Mongolia", - "MO": "Macao", - "MP": "Northern Mariana Islands", - "MQ": "Martinique", - "MR": "Mauritania", - "MS": "Montserrat", - "MT": "Malta", - "MU": "Mauritius", - "MV": "Maldives", - "MW": "Malawi", - "MX": "Mexico", - "MY": "Malaysia", - "MZ": "Mozambique", - "NA": "Namibia", - "NC": "New Caledonia", - "NE": "Niger", - "NF": "Norfolk Island", - "NG": "Nigeria", - "NI": "Nicaragua", - "NL": "Netherlands", - "NO": "Norway", - "NP": "Nepal", - "NR": "Nauru", - "NU": "Niue", - "NZ": "New Zealand", - "OM": "Oman", - "PA": "Panama", - "PE": "Peru", - "PF": "French Polynesia", - "PG": "Papua New Guinea", - "PH": "Philippines", - "PK": "Pakistan", - "PL": "Poland", - "PM": "Saint Pierre and Miquelon", - "PN": "Pitcairn", - "PR": "Puerto Rico", - "PS": "Palestine", - "PT": "Portugal", - "PW": "Palau", - "PY": "Paraguay", - "QA": "Qatar", - "RE": "Réunion", - "RO": "Romania", - "RS": "Serbia", - "RU": "Russia", - "RW": "Rwanda", - "SA": "Saudi Arabia", - "SB": "Solomon Islands", - "SC": "Seychelles", - "SD": "Sudan", - "SE": "Sweden", - "SG": "Singapore", - "SH": "Saint Helena, Ascension and Tristan da Cunha", - "SI": "Slovenia", - "SJ": "Svalbard and Jan Mayen", - "SK": "Slovakia", - "SL": "Sierra Leone", - "SM": "San Marino", - "SN": "Senegal", - "SO": "Somalia", - "SR": "Suriname", - "SS": "South Sudan", - "ST": "Sao Tome and Principe", - "SV": "El Salvador", - "SX": "Sint Maarten (Dutch part)", - "SY": "Syria", - "SZ": "Eswatini", - "TC": "Turks and Caicos Islands", - "TD": "Chad", - "TF": "TAAF", - "TG": "Togo", - "TH": "Thailand", - "TJ": "Tajikistan", - "TK": "Tokelau", - "TL": "Timor-Leste", - "TM": "Turkmenistan", - "TN": "Tunisia", - "TO": "Tonga", - "TR": "Turkey", - "TT": "Trinidad and Tobago", - "TV": "Tuvalu", - "TW": "Taiwan", - "TZ": "Tanzania", - "UA": "Ukraine", - "UG": "Uganda", - "UM": "United States Minor Outlying Islands", - "US": "United States", - "UY": "Uruguay", - "UZ": "Uzbekistan", - "VA": "Holy See", - "VC": "Saint Vincent and the Grenadines", - "VE": "Venezuela", - "VG": "British Virgin Islands", - "VI": "U.S. Virgin Islands", - "VN": "Viet Nam", - "VU": "Vanuatu", - "WF": "Wallis and Futuna", - "WS": "Samoa", - "YE": "Yemen", - "YT": "Mayotte", - "XK": "Kosovo", - "ZA": "South Africa", - "ZM": "Zambia", - "ZW": "Zimbabwe", - "EU": "European Union" - }, - "languages": { - "en-US": "English - US", - "fr": "French", - "nl": "Dutch", - "pt_BR": "Portuguese - Brazil" - } - }, - "KnownHosts": { - "label": "Host", - "add": "Add new host", - "toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}." - }, - "InitializeContainer": { - "title": "DID YOU KNOW", - "subtitle": "<1>Cockatrice is run by volunteers<1>that love card games!" - }, - "LoginContainer": { - "header": { - "title": "Login", - "subtitle": "A cross-platform virtual tabletop for multiplayer card games." - }, - "footer": { - "registerPrompt": "Not registered yet?", - "registerAction": "Create an account", - "credit": "Cockatrice is an open source project", - "version": "Version" - }, - "content": { - "subtitle1": "Play multiplayer card games online.", - "subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free." - }, - "toasts": { - "passwordResetSuccessToast": "Password Reset Successfully", - "accountActivationSuccess": "Account Activated Successfully" - } - }, - "UnsupportedContainer": { - "title": "Unsupported Browser", - "subtitle1": "Please update your browser and/or check your permissions.", - "subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features." - }, - "AccountActivationDialog": { - "title": "Account Activation", - "subtitle1": "Your account has not been activated yet.", - "subtitle2": "You need to provide the activation token received in the activation email." - }, - "KnownHostDialog": { - "title": "{mode, select, edit {Edit} other {Add}} Known Host", - "subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list." - }, - "RegistrationDialog": { - "title": "Create New Account" - }, - "RequestPasswordResetDialog": { - "title": "Request Password Reset" - }, - "ResetPasswordDialog": { - "title": "Reset Password" - }, - "AccountActivationForm": { - "error": { - "failed": "Account activation failed" - }, - "label": { - "activate": "Activate Account" - } - }, - "KnownHostForm": { - "help": "Need help adding a new host?", - "label": { - "add": "Add Host", - "find": "Find Host" - } - }, - "LoginForm": { - "label": { - "autoConnect": "Auto Connect", - "forgot": "Forgot Password", - "login": "Login", - "savePassword": "Save Password", - "savedPassword": "Saved Password" - } - }, - "RegisterForm": { - "label": { - "register": "Register" - }, - "toast": { - "registerSuccess": "Registration Successful!" - } - }, - "RequestPasswordResetForm": { - "error": "Request password reset failed", - "mfaEnabled": "Server has multi-factor authentication enabled", - "request": "Request Reset Token", - "skipRequest": "I already have a reset token" - }, - "ResetPasswordForm": { - "error": "Password reset failed", - "label": { - "reset": "Reset Password" - } - } -} \ No newline at end of file diff --git a/webclient/src/i18n.ts b/webclient/src/i18n.ts deleted file mode 100644 index ef1885965..000000000 --- a/webclient/src/i18n.ts +++ /dev/null @@ -1,32 +0,0 @@ -import i18n from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import ICU from 'i18next-icu'; -import { initReactI18next } from 'react-i18next'; - -import { Language } from 'types'; - -import I18nBackend from './i18n-backend'; - -// Bundle default translation with application -import translation from './i18n-default.json'; - -i18n - .use(ICU) - .use(I18nBackend) - .use(LanguageDetector) - .use(initReactI18next) - // for all options read: https://www.i18next.com/overview/configuration-options - .init({ - fallbackLng: Language['en-US'], - resources: { - [Language['en-US']]: { translation }, - }, - partialBundledLanguages: true, - - interpolation: { - // not needed for react as it escapes by default - escapeValue: false, - } - }); - -export default i18n; diff --git a/webclient/src/images/Images.ts b/webclient/src/images/Images.ts deleted file mode 100644 index 9d08eb3ec..000000000 --- a/webclient/src/images/Images.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Countries } from './countries/_Countries'; -import { Faces } from './faces/_Faces'; -import Logo from './logo.png'; - -export class Images { - static Countries = Countries; - static Faces = Faces; - static Logo = Logo; -} diff --git a/webclient/src/images/countries/.gitignore b/webclient/src/images/countries/.gitignore deleted file mode 100644 index 1337edaa7..000000000 --- a/webclient/src/images/countries/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Ignore all files -* -# Except gitignore -!.gitignore - -!_Countries.ts diff --git a/webclient/src/images/countries/_Countries.ts b/webclient/src/images/countries/_Countries.ts deleted file mode 100644 index a4867db46..000000000 --- a/webclient/src/images/countries/_Countries.ts +++ /dev/null @@ -1,507 +0,0 @@ -// Remove !file-loader! once the following is no longer an issue -// https://github.com/facebook/create-react-app/issues/11770 -import ad from '!file-loader!./ad.svg'; -import ae from '!file-loader!./ae.svg'; -import af from '!file-loader!./af.svg'; -import ag from '!file-loader!./ag.svg'; -import ai from '!file-loader!./ai.svg'; -import al from '!file-loader!./al.svg'; -import am from '!file-loader!./am.svg'; -import ao from '!file-loader!./ao.svg'; -import aq from '!file-loader!./aq.svg'; -import ar from '!file-loader!./ar.svg'; -import as from '!file-loader!./as.svg'; -import at from '!file-loader!./at.svg'; -import au from '!file-loader!./au.svg'; -import aw from '!file-loader!./aw.svg'; -import ax from '!file-loader!./ax.svg'; -import az from '!file-loader!./az.svg'; -import ba from '!file-loader!./ba.svg'; -import bb from '!file-loader!./bb.svg'; -import bd from '!file-loader!./bd.svg'; -import be from '!file-loader!./be.svg'; -import bf from '!file-loader!./bf.svg'; -import bg from '!file-loader!./bg.svg'; -import bh from '!file-loader!./bh.svg'; -import bi from '!file-loader!./bi.svg'; -import bj from '!file-loader!./bj.svg'; -import bl from '!file-loader!./bl.svg'; -import bm from '!file-loader!./bm.svg'; -import bn from '!file-loader!./bn.svg'; -import bo from '!file-loader!./bo.svg'; -import bq from '!file-loader!./bq.svg'; -import br from '!file-loader!./br.svg'; -import bs from '!file-loader!./bs.svg'; -import bt from '!file-loader!./bt.svg'; -import bv from '!file-loader!./bv.svg'; -import bw from '!file-loader!./bw.svg'; -import by from '!file-loader!./by.svg'; -import bz from '!file-loader!./bz.svg'; -import ca from '!file-loader!./ca.svg'; -import cc from '!file-loader!./cc.svg'; -import cd from '!file-loader!./cd.svg'; -import cf from '!file-loader!./cf.svg'; -import cg from '!file-loader!./cg.svg'; -import ch from '!file-loader!./ch.svg'; -import ci from '!file-loader!./ci.svg'; -import ck from '!file-loader!./ck.svg'; -import cl from '!file-loader!./cl.svg'; -import cm from '!file-loader!./cm.svg'; -import cn from '!file-loader!./cn.svg'; -import co from '!file-loader!./co.svg'; -import cr from '!file-loader!./cr.svg'; -import cu from '!file-loader!./cu.svg'; -import cv from '!file-loader!./cv.svg'; -import cw from '!file-loader!./cw.svg'; -import cx from '!file-loader!./cx.svg'; -import cy from '!file-loader!./cy.svg'; -import cz from '!file-loader!./cz.svg'; -import de from '!file-loader!./de.svg'; -import dj from '!file-loader!./dj.svg'; -import dk from '!file-loader!./dk.svg'; -import dm from '!file-loader!./dm.svg'; -import _do from '!file-loader!./do.svg'; -import dz from '!file-loader!./dz.svg'; -import ec from '!file-loader!./ec.svg'; -import ee from '!file-loader!./ee.svg'; -import eg from '!file-loader!./eg.svg'; -import eh from '!file-loader!./eh.svg'; -import er from '!file-loader!./er.svg'; -import es from '!file-loader!./es.svg'; -import et from '!file-loader!./et.svg'; -import eu from '!file-loader!./eu.svg'; -import fi from '!file-loader!./fi.svg'; -import fj from '!file-loader!./fj.svg'; -import fk from '!file-loader!./fk.svg'; -import fm from '!file-loader!./fm.svg'; -import fo from '!file-loader!./fo.svg'; -import fr from '!file-loader!./fr.svg'; -import ga from '!file-loader!./ga.svg'; -import gb from '!file-loader!./gb.svg'; -import gd from '!file-loader!./gd.svg'; -import ge from '!file-loader!./ge.svg'; -import gf from '!file-loader!./gf.svg'; -import gg from '!file-loader!./gg.svg'; -import gh from '!file-loader!./gh.svg'; -import gi from '!file-loader!./gi.svg'; -import gl from '!file-loader!./gl.svg'; -import gm from '!file-loader!./gm.svg'; -import gn from '!file-loader!./gn.svg'; -import gp from '!file-loader!./gp.svg'; -import gq from '!file-loader!./gq.svg'; -import gr from '!file-loader!./gr.svg'; -import gs from '!file-loader!./gs.svg'; -import gt from '!file-loader!./gt.svg'; -import gu from '!file-loader!./gu.svg'; -import gw from '!file-loader!./gw.svg'; -import gy from '!file-loader!./gy.svg'; -import hk from '!file-loader!./hk.svg'; -import hm from '!file-loader!./hm.svg'; -import hn from '!file-loader!./hn.svg'; -import hr from '!file-loader!./hr.svg'; -import ht from '!file-loader!./ht.svg'; -import hu from '!file-loader!./hu.svg'; -import id from '!file-loader!./id.svg'; -import ie from '!file-loader!./ie.svg'; -import il from '!file-loader!./il.svg'; -import im from '!file-loader!./im.svg'; -import _in from '!file-loader!./in.svg'; -import io from '!file-loader!./io.svg'; -import iq from '!file-loader!./iq.svg'; -import ir from '!file-loader!./ir.svg'; -import is from '!file-loader!./is.svg'; -import it from '!file-loader!./it.svg'; -import je from '!file-loader!./je.svg'; -import jm from '!file-loader!./jm.svg'; -import jo from '!file-loader!./jo.svg'; -import jp from '!file-loader!./jp.svg'; -import ke from '!file-loader!./ke.svg'; -import kg from '!file-loader!./kg.svg'; -import kh from '!file-loader!./kh.svg'; -import ki from '!file-loader!./ki.svg'; -import km from '!file-loader!./km.svg'; -import kn from '!file-loader!./kn.svg'; -import kp from '!file-loader!./kp.svg'; -import kr from '!file-loader!./kr.svg'; -import kw from '!file-loader!./kw.svg'; -import ky from '!file-loader!./ky.svg'; -import kz from '!file-loader!./kz.svg'; -import la from '!file-loader!./la.svg'; -import lb from '!file-loader!./lb.svg'; -import lc from '!file-loader!./lc.svg'; -import li from '!file-loader!./li.svg'; -import lk from '!file-loader!./lk.svg'; -import lr from '!file-loader!./lr.svg'; -import ls from '!file-loader!./ls.svg'; -import lt from '!file-loader!./lt.svg'; -import lu from '!file-loader!./lu.svg'; -import lv from '!file-loader!./lv.svg'; -import ly from '!file-loader!./ly.svg'; -import ma from '!file-loader!./ma.svg'; -import mc from '!file-loader!./mc.svg'; -import md from '!file-loader!./md.svg'; -import me from '!file-loader!./me.svg'; -import mf from '!file-loader!./mf.svg'; -import mg from '!file-loader!./mg.svg'; -import mh from '!file-loader!./mh.svg'; -import mk from '!file-loader!./mk.svg'; -import ml from '!file-loader!./ml.svg'; -import mm from '!file-loader!./mm.svg'; -import mn from '!file-loader!./mn.svg'; -import mo from '!file-loader!./mo.svg'; -import mp from '!file-loader!./mp.svg'; -import mq from '!file-loader!./mq.svg'; -import mr from '!file-loader!./mr.svg'; -import ms from '!file-loader!./ms.svg'; -import mt from '!file-loader!./mt.svg'; -import mu from '!file-loader!./mu.svg'; -import mv from '!file-loader!./mv.svg'; -import mw from '!file-loader!./mw.svg'; -import mx from '!file-loader!./mx.svg'; -import my from '!file-loader!./my.svg'; -import mz from '!file-loader!./mz.svg'; -import na from '!file-loader!./na.svg'; -import nc from '!file-loader!./nc.svg'; -import ne from '!file-loader!./ne.svg'; -import nf from '!file-loader!./nf.svg'; -import ng from '!file-loader!./ng.svg'; -import ni from '!file-loader!./ni.svg'; -import nl from '!file-loader!./nl.svg'; -import no from '!file-loader!./no.svg'; -import np from '!file-loader!./np.svg'; -import nr from '!file-loader!./nr.svg'; -import nu from '!file-loader!./nu.svg'; -import nz from '!file-loader!./nz.svg'; -import om from '!file-loader!./om.svg'; -import pa from '!file-loader!./pa.svg'; -import pe from '!file-loader!./pe.svg'; -import pf from '!file-loader!./pf.svg'; -import pg from '!file-loader!./pg.svg'; -import ph from '!file-loader!./ph.svg'; -import pk from '!file-loader!./pk.svg'; -import pl from '!file-loader!./pl.svg'; -import pm from '!file-loader!./pm.svg'; -import pn from '!file-loader!./pn.svg'; -import pr from '!file-loader!./pr.svg'; -import ps from '!file-loader!./ps.svg'; -import pt from '!file-loader!./pt.svg'; -import pw from '!file-loader!./pw.svg'; -import py from '!file-loader!./py.svg'; -import qa from '!file-loader!./qa.svg'; -import re from '!file-loader!./re.svg'; -import ro from '!file-loader!./ro.svg'; -import rs from '!file-loader!./rs.svg'; -import ru from '!file-loader!./ru.svg'; -import rw from '!file-loader!./rw.svg'; -import sa from '!file-loader!./sa.svg'; -import sb from '!file-loader!./sb.svg'; -import sc from '!file-loader!./sc.svg'; -import sd from '!file-loader!./sd.svg'; -import se from '!file-loader!./se.svg'; -import sg from '!file-loader!./sg.svg'; -import sh from '!file-loader!./sh.svg'; -import si from '!file-loader!./si.svg'; -import sj from '!file-loader!./sj.svg'; -import sk from '!file-loader!./sk.svg'; -import sl from '!file-loader!./sl.svg'; -import sm from '!file-loader!./sm.svg'; -import sn from '!file-loader!./sn.svg'; -import so from '!file-loader!./so.svg'; -import sr from '!file-loader!./sr.svg'; -import ss from '!file-loader!./ss.svg'; -import st from '!file-loader!./st.svg'; -import sv from '!file-loader!./sv.svg'; -import sx from '!file-loader!./sx.svg'; -import sy from '!file-loader!./sy.svg'; -import sz from '!file-loader!./sz.svg'; -import tc from '!file-loader!./tc.svg'; -import td from '!file-loader!./td.svg'; -import tf from '!file-loader!./tf.svg'; -import tg from '!file-loader!./tg.svg'; -import th from '!file-loader!./th.svg'; -import tj from '!file-loader!./tj.svg'; -import tk from '!file-loader!./tk.svg'; -import tl from '!file-loader!./tl.svg'; -import tm from '!file-loader!./tm.svg'; -import tn from '!file-loader!./tn.svg'; -import to from '!file-loader!./to.svg'; -import tr from '!file-loader!./tr.svg'; -import tt from '!file-loader!./tt.svg'; -import tv from '!file-loader!./tv.svg'; -import tw from '!file-loader!./tw.svg'; -import tz from '!file-loader!./tz.svg'; -import ua from '!file-loader!./ua.svg'; -import ug from '!file-loader!./ug.svg'; -import um from '!file-loader!./um.svg'; -import us from '!file-loader!./us.svg'; -import uy from '!file-loader!./uy.svg'; -import uz from '!file-loader!./uz.svg'; -import va from '!file-loader!./va.svg'; -import vc from '!file-loader!./vc.svg'; -import ve from '!file-loader!./ve.svg'; -import vg from '!file-loader!./vg.svg'; -import vi from '!file-loader!./vi.svg'; -import vn from '!file-loader!./vn.svg'; -import vu from '!file-loader!./vu.svg'; -import wf from '!file-loader!./wf.svg'; -import ws from '!file-loader!./ws.svg'; -import xk from '!file-loader!./xk.svg'; -import ye from '!file-loader!./ye.svg'; -import yt from '!file-loader!./yt.svg'; -import za from '!file-loader!./za.svg'; -import zm from '!file-loader!./zm.svg'; -import zw from '!file-loader!./zw.svg'; - -export const Countries = { - ad, - ae, - af, - ag, - ai, - al, - am, - ao, - aq, - ar, - as, - at, - au, - aw, - ax, - az, - ba, - bb, - bd, - be, - bf, - bg, - bh, - bi, - bj, - bl, - bm, - bn, - bo, - bq, - br, - bs, - bt, - bv, - bw, - by, - bz, - ca, - cc, - cd, - cf, - cg, - ch, - ci, - ck, - cl, - cm, - cn, - co, - cr, - cu, - cv, - cw, - cx, - cy, - cz, - de, - dj, - dk, - dm, - do: _do, - dz, - ec, - ee, - eg, - eh, - er, - es, - et, - eu, - fi, - fj, - fk, - fm, - fo, - fr, - ga, - gb, - gd, - ge, - gf, - gg, - gh, - gi, - gl, - gm, - gn, - gp, - gq, - gr, - gs, - gt, - gu, - gw, - gy, - hk, - hm, - hn, - hr, - ht, - hu, - id, - ie, - il, - im, - in: _in, - io, - iq, - ir, - is, - it, - je, - jm, - jo, - jp, - ke, - kg, - kh, - ki, - km, - kn, - kp, - kr, - kw, - ky, - kz, - la, - lb, - lc, - li, - lk, - lr, - ls, - lt, - lu, - lv, - ly, - ma, - mc, - md, - me, - mf, - mg, - mh, - mk, - ml, - mm, - mn, - mo, - mp, - mq, - mr, - ms, - mt, - mu, - mv, - mw, - mx, - my, - mz, - na, - nc, - ne, - nf, - ng, - ni, - nl, - no, - np, - nr, - nu, - nz, - om, - pa, - pe, - pf, - pg, - ph, - pk, - pl, - pm, - pn, - pr, - ps, - pt, - pw, - py, - qa, - re, - ro, - rs, - ru, - rw, - sa, - sb, - sc, - sd, - se, - sg, - sh, - si, - sj, - sk, - sl, - sm, - sn, - so, - sr, - ss, - st, - sv, - sx, - sy, - sz, - tc, - td, - tf, - tg, - th, - tj, - tk, - tl, - tm, - tn, - to, - tr, - tt, - tv, - tw, - tz, - ua, - ug, - um, - us, - uy, - uz, - va, - vc, - ve, - vg, - vi, - vn, - vu, - wf, - ws, - xk, - ye, - yt, - za, - zm, - zw, -}; diff --git a/webclient/src/images/faces/_Faces.ts b/webclient/src/images/faces/_Faces.ts deleted file mode 100644 index 73a8c361b..000000000 --- a/webclient/src/images/faces/_Faces.ts +++ /dev/null @@ -1,9 +0,0 @@ -import face1 from './face1.jpg'; -import face2 from './face2.jpg'; -import face3 from './face3.jpg'; - -export const Faces = { - face1, - face2, - face3, -} diff --git a/webclient/src/images/faces/face1.jpg b/webclient/src/images/faces/face1.jpg deleted file mode 100644 index 931c5822d..000000000 Binary files a/webclient/src/images/faces/face1.jpg and /dev/null differ diff --git a/webclient/src/images/faces/face2.jpg b/webclient/src/images/faces/face2.jpg deleted file mode 100644 index 8b5bea967..000000000 Binary files a/webclient/src/images/faces/face2.jpg and /dev/null differ diff --git a/webclient/src/images/faces/face3.jpg b/webclient/src/images/faces/face3.jpg deleted file mode 100644 index ace651dc0..000000000 Binary files a/webclient/src/images/faces/face3.jpg and /dev/null differ diff --git a/webclient/src/images/index.ts b/webclient/src/images/index.ts deleted file mode 100644 index e96072916..000000000 --- a/webclient/src/images/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Images'; diff --git a/webclient/src/images/logo.png b/webclient/src/images/logo.png deleted file mode 100644 index 7ce83bd20..000000000 Binary files a/webclient/src/images/logo.png and /dev/null differ diff --git a/webclient/src/index.css b/webclient/src/index.css deleted file mode 100644 index c5f9b9230..000000000 --- a/webclient/src/index.css +++ /dev/null @@ -1,70 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Teko&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap'); - -:root { - -} - -* { - box-sizing: border-box; -} - -html, -body, -#root { - height: 100%; -} - -body { - margin: 0; - font-family: - -apple-system, - BlinkMacSystemFont, - "Open Sans", - "Segoe UI", - "Roboto", - "Oxygen", - "Ubuntu", - "Cantarell", - "Fira Sans", - "Droid Sans", - "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} - -strong, -b { - font-weight: bold; -} - -a { - color: inherit; - text-decoration: none; -} - -.overflow-scroll { - overflow-y: scroll; /* has to be scroll, not auto */ - -webkit-overflow-scrolling: touch; -} - -.plain-link { - color: inherit; - text-decoration: none; -} - -.single-line-ellipsis { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.disabled-link { - pointer-events: none; -} diff --git a/webclient/src/index.tsx b/webclient/src/index.tsx deleted file mode 100644 index 97b5bb014..000000000 --- a/webclient/src/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { Theme, StyledEngineProvider } from '@mui/material'; -import { ThemeProvider } from '@mui/material/styles'; - -import { AppShell } from './containers'; -import { materialTheme } from './material-theme'; - -import './i18n'; -import './index.css'; - -const AppWithMaterialTheme = () => ( - - - - - - - -); - -const container = document.getElementById('root'); -const root = createRoot(container!); - -root.render(); diff --git a/webclient/src/material-theme.ts b/webclient/src/material-theme.ts deleted file mode 100644 index beee1146e..000000000 --- a/webclient/src/material-theme.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { PaletteMode } from '@mui/material'; -import { createTheme } from '@mui/material/styles'; - -const mode: PaletteMode = 'light'; - -const palette = { - mode, - - background: { - default: 'rgb(35, 35, 35)', - paper: '#FFFFFF', - }, - primary: { - main: '#7033DB', - light: 'rgba(112, 51, 219, .3)', - dark: '#401C7F', - contrastText: '#FFFFFF', - }, - grey: { - 50: '#fafafa', - 100: '#f5f5f5', - 200: '#eeeeee', - 300: '#e0e0e0', - 400: '#bdbdbd', - 500: '#9e9e9e', - 600: '#757575', - 700: '#616161', - 800: '#424242', - 900: '#212121', - A100: '#d5d5d5', - A200: '#aaaaaa', - A400: '#303030', - A700: '#616161', - }, - // secondary: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // error: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // warning: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // info: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - success: { - main: '#6CDF39', - light: '#6CDF39', - // dark: '', - // contrastText: '', - }, -}; - -export const materialTheme = createTheme({ - palette, - - components: { - MuiCssBaseline: { - styleOverrides: { - '@global': { - '@font-face': [], - }, - }, - }, - MuiButton: { - styleOverrides: { - root: { - fontWeight: 'bold', - textTransform: 'none', - - '&.rounded': { - // 'border-radius': '50px', - }, - - '&.tall': { - 'height': '40px', - }, - }, - }, - }, - - MuiCheckbox: { - styleOverrides: { - root: { - '& .MuiSvgIcon-root': { - width: '.75em', - height: '.75em', - }, - }, - }, - }, - - MuiFormControlLabel: { - styleOverrides: { - label: { - fontSize: 12, - fontWeight: 'bold', - color: palette.primary.main, - }, - }, - }, - - MuiLink: { - styleOverrides: { - root: { - fontWeight: 'bold', - }, - }, - }, - - MuiList: { - styleOverrides: { - root: { - padding: '8px', - - '&.MuiList-padding': { - paddingBottom: '4px', - }, - - '& .MuiButton-root': { - width: '100%', - }, - - '& > .MuiButtonBase-root': { - padding: '8px 16px', - marginBottom: '4px', - borderRadius: 0, - justifyContent: 'space-between', - }, - - '& .MuiButtonBase-root.Mui-selected, & .MuiButtonBase-root.Mui-selected:focus': { - background: 'none', - fontWeight: 'bold', - }, - - ['& .MuiButtonBase-root:hover, & .MuiButtonBase-root.Mui-selected:hover']: { - background: palette.primary.light - }, - }, - }, - }, - - MuiListItem: { - styleOverrides: { - root: { - }, - } - }, - - MuiInputBase: { - styleOverrides: { - formControl: { - '& .MuiSelect-root svg': { - display: 'none', - }, - }, - }, - }, - - MuiOutlinedInput: { - styleOverrides: { - root: { - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderWidth: '1px', - }, - - '.rounded &': { - // 'border-radius': '50px', - }, - - '.tall &': { - height: '40px', - }, - }, - }, - }, - }, - - typography: { - fontSize: 12, - fontFamily: 'Open Sans, sans-serif', - - h1: { - fontSize: 28, - fontWeight: 'bold', - }, - h2: { - fontSize: 24, - fontWeight: 'bold', - }, - // h3: {}, - // h4: {}, - // h5: {}, - // h6: {}, - subtitle1: { - fontSize: 14, - fontWeight: 'bold', - lineHeight: 1.4, - color: palette.grey[500], - }, - subtitle2: { - lineHeight: 1.4, - color: palette.grey[500], - }, - body1: { - fontSize: '.75rem', - lineHeight: 1.4, - }, - // body2: {}, - // button: {}, - // caption: {}, - // overline: {}, - }, - - spacing: 8, - - breakpoints: { - values: { - xs: 0, - sm: 640, - md: 768, - lg: 1024, - xl: 1280, - }, - }, -}); diff --git a/webclient/src/react-app-env.d.ts b/webclient/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc..000000000 --- a/webclient/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/webclient/src/services/CardImporterService.ts b/webclient/src/services/CardImporterService.ts deleted file mode 100644 index 691e86f0e..000000000 --- a/webclient/src/services/CardImporterService.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Fetch and parse card sets - -class CardImporterService { - importCards(url): Promise { - const error = 'Card import must be in valid MTG JSON format'; - - return fetch(url) - .then(response => { - if (response.headers.get('Content-Type') !== 'application/json') { - throw new Error(error); - } - - return response.json(); - }) - .then((json) => { - try { - const sortedSets = Object.keys(json.data) - .map(key => json.data[key]) - .sort((a, b) => new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime()); - - const sets = sortedSets.map(({ cards, tokens, ...set }) => ({ - ...set, - cards: cards.map(({ name }) => name), - tokens: tokens.map(({ name }) => name), - })); - - const unsortedCards = sortedSets.reduce((acc, set) => { - set.cards.forEach(card => acc[card.name] = card); - return acc; - }, {}); - - const cards = Object.keys(unsortedCards) - .sort((a, b) => a.localeCompare(b)) - .map(key => unsortedCards[key]); - - return { cards, sets }; - } catch (e) { - throw new Error(error); - } - }); - } - - importTokens(url): Promise { - const error = 'Token import must be in valid MTG XML format'; - - return fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('Failed to fetch'); - } - - return response.text() - }) - .then((xmlString) => { - try { - const parser = new DOMParser(); - const dom = parser.parseFromString(xmlString, 'application/xml'); - - const tokens = Array.from(dom.querySelectorAll('card')).map( - (tokenElement) => this.parseXmlAttributes(tokenElement) - ); - - return tokens; - } catch (e) { - throw new Error(error); - } - }) - } - - private parseXmlAttributes(dom: Element) { - return Array.from(dom.children).reduce((attributes, child) => { - const value = child.children.length ? this.parseXmlAttributes(child) : child.innerHTML; - - let parsedAttributes = { value }; - - if (child.attributes.length) { - const childAttributes = Array.from(child.attributes).reduce((acc, { name, value }) => { - acc[name] = value; - return acc; - }, {}); - - parsedAttributes = { - ...parsedAttributes, - ...childAttributes, - }; - } - - // @TODO: clean this up and normalize what i'm returning - if (attributes[child.tagName]) { - if (Array.isArray(attributes[child.tagName])) { - attributes[child.tagName].push(parsedAttributes) - } else { - attributes[child.tagName] = [parsedAttributes]; - } - } else { - attributes[child.tagName] = parsedAttributes; - } - - return attributes; - }, {}); - } -} - -export const cardImporterService = new CardImporterService(); diff --git a/webclient/src/services/ServerProps.ts b/webclient/src/services/ServerProps.ts deleted file mode 100644 index 0d8c3d652..000000000 --- a/webclient/src/services/ServerProps.ts +++ /dev/null @@ -1,9 +0,0 @@ -import props from '../server-props.json'; - -class ServerProps { - get REACT_APP_VERSION() { - return props?.REACT_APP_VERSION; - } -} - -export const serverProps = new ServerProps(); diff --git a/webclient/src/services/dexie/DexieDTOs/CardDTO.ts b/webclient/src/services/dexie/DexieDTOs/CardDTO.ts deleted file mode 100644 index a060aedb1..000000000 --- a/webclient/src/services/dexie/DexieDTOs/CardDTO.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Card } from 'types'; - -import { dexieService } from '../DexieService'; - -export class CardDTO extends Card { - save() { - return dexieService.cards.put(this); - } - - static get(name) { - return dexieService.cards.where('name').equalsIgnoreCase(name).first(); - } - - static bulkAdd(cards: CardDTO[]): Promise { - return dexieService.cards.bulkPut(cards); - } -}; - -dexieService.cards.mapToClass(CardDTO); diff --git a/webclient/src/services/dexie/DexieDTOs/HostDTO.ts b/webclient/src/services/dexie/DexieDTOs/HostDTO.ts deleted file mode 100644 index 17f600626..000000000 --- a/webclient/src/services/dexie/DexieDTOs/HostDTO.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { IndexableType } from 'dexie'; -import { Host } from 'types'; - -import { dexieService } from '../DexieService'; - -export class HostDTO extends Host { - save() { - return dexieService.hosts.put(this); - } - - static add(host: Host): Promise { - return dexieService.hosts.add(host); - } - - static get(id: number): Promise { - return dexieService.hosts.where('id').equals(id).first(); - } - - static getAll(): Promise { - return dexieService.hosts.toArray(); - } - - static bulkAdd(hosts: Host[]): Promise { - return dexieService.hosts.bulkAdd(hosts); - } - - static delete(id: string): Promise { - return dexieService.hosts.delete(id); - } -}; - -dexieService.hosts.mapToClass(HostDTO); diff --git a/webclient/src/services/dexie/DexieDTOs/SetDTO.ts b/webclient/src/services/dexie/DexieDTOs/SetDTO.ts deleted file mode 100644 index 6ae2dd20a..000000000 --- a/webclient/src/services/dexie/DexieDTOs/SetDTO.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Set } from 'types'; - -import { dexieService } from '../DexieService'; - -export class SetDTO extends Set { - save() { - return dexieService.sets.put(this); - } - - static get(name) { - return dexieService.sets.where('name').equalsIgnoreCase(name).first(); - } - - static bulkAdd(sets: SetDTO[]): Promise { - return dexieService.sets.bulkPut(sets); - } -}; - -dexieService.sets.mapToClass(SetDTO); diff --git a/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts b/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts deleted file mode 100644 index bbfa1680d..000000000 --- a/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Setting } from 'types'; - -import { dexieService } from '../DexieService'; - -export class SettingDTO extends Setting { - constructor(user) { - super(); - - this.user = user; - this.autoConnect = false; - } - - save() { - return dexieService.settings.put(this); - } - - static get(user) { - return dexieService.settings.where('user').equalsIgnoreCase(user).first(); - } -}; - -dexieService.settings.mapToClass(SettingDTO); diff --git a/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts b/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts deleted file mode 100644 index 35d537709..000000000 --- a/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Token } from 'types'; - -import { dexieService } from '../DexieService'; - -export class TokenDTO extends Token { - save() { - return dexieService.tokens.put(this); - } - - static get(name) { - return dexieService.tokens.where('name.value').equalsIgnoreCase(name).first(); - } - - static bulkAdd(tokens: TokenDTO[]): Promise { - return dexieService.tokens.bulkPut(tokens); - } -}; - -dexieService.tokens.mapToClass(TokenDTO); diff --git a/webclient/src/services/dexie/DexieDTOs/index.ts b/webclient/src/services/dexie/DexieDTOs/index.ts deleted file mode 100644 index 9121f1f4b..000000000 --- a/webclient/src/services/dexie/DexieDTOs/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './CardDTO'; -export * from './SetDTO'; -export * from './SettingDTO'; -export * from './TokenDTO'; -export * from './HostDTO'; diff --git a/webclient/src/services/dexie/DexieSchemas/v1.schema.ts b/webclient/src/services/dexie/DexieSchemas/v1.schema.ts deleted file mode 100644 index 18e7f35c4..000000000 --- a/webclient/src/services/dexie/DexieSchemas/v1.schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -export enum Stores { - SETTINGS = 'settings', - CARDS = 'cards', - SETS = 'sets', - TOKENS = 'tokens', - HOSTS = 'hosts', -} - -export const schemaV1 = (db) => { - db.version(1).stores({ - [Stores.CARDS]: 'name', - [Stores.SETS]: 'code', - [Stores.SETTINGS]: 'user', - [Stores.TOKENS]: 'name.value', - [Stores.HOSTS]: '++id', - }); -} diff --git a/webclient/src/services/dexie/DexieService.ts b/webclient/src/services/dexie/DexieService.ts deleted file mode 100644 index 9dcd3b941..000000000 --- a/webclient/src/services/dexie/DexieService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Dexie from 'dexie'; - -import { Stores, schemaV1 } from './DexieSchemas/v1.schema'; - -class DexieService { - private db: Dexie = new Dexie('Webatrice'); - - constructor() { - schemaV1(this.db); - } - - get settings() { - return this.db.table(Stores.SETTINGS); - } - - get cards() { - return this.db.table(Stores.CARDS); - } - - get sets() { - return this.db.table(Stores.SETS); - } - - get tokens() { - return this.db.table(Stores.TOKENS); - } - - get hosts() { - return this.db.table(Stores.HOSTS); - } - - testConnection() { - return this.db.open(); - } -} - -export const dexieService = new DexieService(); diff --git a/webclient/src/services/dexie/index.ts b/webclient/src/services/dexie/index.ts deleted file mode 100644 index 593f19397..000000000 --- a/webclient/src/services/dexie/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DexieDTOs'; -export * from './DexieService'; diff --git a/webclient/src/services/index.ts b/webclient/src/services/index.ts deleted file mode 100644 index ebebeb65e..000000000 --- a/webclient/src/services/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './CardImporterService'; -export * from './ServerProps'; -export * from './dexie'; diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts deleted file mode 100644 index c4e40e2ec..000000000 --- a/webclient/src/setupTests.ts +++ /dev/null @@ -1,10 +0,0 @@ -import protobuf from 'protobufjs'; - -// ensure jest-dom is always available during testing to cut down on boilerplate -import '@testing-library/jest-dom'; - -class MockProtobufRoot { - load() {} -} - -(protobuf as any).Root = MockProtobufRoot; diff --git a/webclient/src/store/actions/actionReducer.ts b/webclient/src/store/actions/actionReducer.ts deleted file mode 100644 index a01dba984..000000000 --- a/webclient/src/store/actions/actionReducer.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @author Luke Brandon Farrell - * @description Application reducer. - */ - -import { AnyAction } from 'redux' - - interface InitialState { - type: string | null - payload: any - meta: any - error: boolean - count: number - } - -/** - * Initial data. - */ -const initialState: InitialState = { - type: null, - payload: null, - meta: null, - error: false, - count: 0, -} - -/** - * Calculates the application state. - * - * @param state - * @param action - * @return {*} - */ -export const actionReducer = ( - state = initialState, - action: AnyAction, -): InitialState => { - return { - ...state, - ...action, - count: state.count + 1, - } -} diff --git a/webclient/src/store/actions/index.ts b/webclient/src/store/actions/index.ts deleted file mode 100644 index 4c5f9ef48..000000000 --- a/webclient/src/store/actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { actionReducer } from './actionReducer'; diff --git a/webclient/src/store/common/SortUtil.ts b/webclient/src/store/common/SortUtil.ts deleted file mode 100644 index 56910259d..000000000 --- a/webclient/src/store/common/SortUtil.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { SortBy, SortDirection, User } from 'types'; - -export default class SortUtil { - static sortByField(arr: any[], sortBy: SortBy): void { - if (arr.length) { - const field = SortUtil.resolveFieldChain(arr[0], sortBy.field); - const fieldType = typeof field; - - if (fieldType === 'string') { - SortUtil.sortByString(arr, sortBy); - return; - } - - if (fieldType === 'number') { - SortUtil.sortByNumber(arr, sortBy); - return; - } - - throw new Error('SortField must resolve to either a string or number'); - } - } - - static sortByFields(arr: any[], sorts: SortBy[]) { - if (arr.length) { - arr.sort((a, b) => { - for (let i = 0; i < sorts.length; i++) { - const sortBy = sorts[i]; - const field = SortUtil.resolveFieldChain(arr[0], sortBy.field); - - const fieldType = typeof field; - - if (fieldType === 'string') { - const result = SortUtil.stringComparator(a, b, sortBy); - - if (result) { - return result; - } - } - - if (fieldType === 'number') { - const result = SortUtil.numberComparator(a, b, sortBy); - - if (result) { - return result; - } - } - - throw new Error('SortField must resolve to either a string or number'); - } - - return 0; - }) - } - } - - static sortUsersByField(users: User[], sortBy: SortBy) { - if (users.length) { - users.sort((a, b) => SortUtil.userComparator(a, b, sortBy)) - } - } - - static toggleSortBy(field: string, sortBy: SortBy) { - const sameField = field === sortBy.field; - const isASC = sortBy.order === SortDirection.ASC; - - return { - field, - order: sameField && isASC ? SortDirection.DESC : SortDirection.ASC - } - } - - private static sortByNumber(arr: any[], sortBy: SortBy): void { - arr.sort((a, b) => SortUtil.numberComparator(a, b, sortBy)); - } - - private static sortByString(arr: any[], sortBy: SortBy): void { - arr.sort((a, b) => SortUtil.stringComparator(a, b, sortBy)); - } - - private static userComparator(a, b, sortBy, sortByUserLevel = true) { - if (sortByUserLevel) { - const adminSortBy = { - field: 'userLevel', - order: SortDirection.DESC - }; - - const adminSorted = SortUtil.numberComparator(a, b, adminSortBy); - - if (adminSorted) { - return adminSorted; - } - } - - const sorted = SortUtil.stringComparator(a, b, sortBy); - - if (sorted) { - return sorted; - } - - return 0; - } - - private static numberComparator(a, b, { field, order }: SortBy) { - const aResolved = SortUtil.resolveFieldChain(a, field); - const bResolved = SortUtil.resolveFieldChain(b, field); - - if (order === SortDirection.ASC) { - return aResolved - bResolved; - } else { - return bResolved - aResolved; - } - } - - private static stringComparator(a, b, { field, order }: SortBy) { - const aResolved = SortUtil.resolveFieldChain(a, field); - const bResolved = SortUtil.resolveFieldChain(b, field); - - // Force empty strings to sort to bottom - if (!aResolved && !bResolved) { - return 0; - } - if (!aResolved) { - return 1; - } - if (!bResolved) { - return -1; - } - - if (order === SortDirection.ASC) { - return aResolved.localeCompare(bResolved); - } else { - return bResolved.localeCompare(aResolved); - } - } - - private static resolveFieldChain(obj: object, field: string) { - const links = field.split('.'); - - if (links.length > 1) { - return links.reduce((obj, link) => { - const parsed = parseInt(link, 10); - - if (parsed.toLocaleString() === 'NaN') { - return obj[link]; - } else { - return obj[parsed]; - } - }, obj) || null; - } else { - return obj[field]; - } - } -} diff --git a/webclient/src/store/common/index.ts b/webclient/src/store/common/index.ts deleted file mode 100644 index 340676b6d..000000000 --- a/webclient/src/store/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SortUtil } from './SortUtil'; diff --git a/webclient/src/store/index.ts b/webclient/src/store/index.ts deleted file mode 100644 index b43f290c5..000000000 --- a/webclient/src/store/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { store } from './store'; - -// Common -export { SortUtil } from './common'; - -// Server - -export { - Types as ServerTypes, - Selectors as ServerSelectors, - Dispatch as ServerDispatch } from './server'; - -export * from 'store/server/server.interfaces'; - -export { - Types as RoomsTypes, - Selectors as RoomsSelectors, - Dispatch as RoomsDispatch } from 'store/rooms'; - -export * from 'store/rooms/rooms.interfaces'; - - diff --git a/webclient/src/store/rooms/index.ts b/webclient/src/store/rooms/index.ts deleted file mode 100644 index 3ebe3905a..000000000 --- a/webclient/src/store/rooms/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './rooms.actions'; -export * from './rooms.dispatch'; -export * from './rooms.reducer'; -export * from './rooms.selectors'; -export * from './rooms.types'; diff --git a/webclient/src/store/rooms/rooms.actions.tsx b/webclient/src/store/rooms/rooms.actions.tsx deleted file mode 100644 index 98108fe8b..000000000 --- a/webclient/src/store/rooms/rooms.actions.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Types } from './rooms.types'; - -export const Actions = { - clearStore: () => ({ - type: Types.CLEAR_STORE - }), - - updateRooms: rooms => ({ - type: Types.UPDATE_ROOMS, - rooms - }), - - joinRoom: roomInfo => ({ - type: Types.JOIN_ROOM, - roomInfo - }), - - leaveRoom: roomId => ({ - type: Types.LEAVE_ROOM, - roomId - }), - - addMessage: (roomId, message) => ({ - type: Types.ADD_MESSAGE, - roomId, - message - }), - - updateGames: (roomId, games) => ({ - type: Types.UPDATE_GAMES, - roomId, - games - }), - - userJoined: (roomId, user) => ({ - type: Types.USER_JOINED, - roomId, - user - }), - - userLeft: (roomId, name) => ({ - type: Types.USER_LEFT, - roomId, - name - }), - - sortGames: (roomId, field, order) => ({ - type: Types.SORT_GAMES, - roomId, - field, - order - }), - - removeMessages: (roomId, name, amount) => ({ - type: Types.REMOVE_MESSAGES, - roomId, - name, - amount - }), - - gameCreated: (roomId) => ({ - type: Types.GAME_CREATED, - roomId - }), - - joinedGame: (roomId, gameId) => ({ - type: Types.JOINED_GAME, - roomId, - gameId - }), -} diff --git a/webclient/src/store/rooms/rooms.dispatch.tsx b/webclient/src/store/rooms/rooms.dispatch.tsx deleted file mode 100644 index c89b5ebc4..000000000 --- a/webclient/src/store/rooms/rooms.dispatch.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { reset } from 'redux-form'; -import { Actions } from './rooms.actions'; -import { store } from 'store'; - -export const Dispatch = { - clearStore: () => { - store.dispatch(Actions.clearStore()); - }, - - updateRooms: rooms => { - store.dispatch(Actions.updateRooms(rooms)); - }, - - joinRoom: roomInfo => { - store.dispatch(Actions.joinRoom(roomInfo)); - - }, - - leaveRoom: roomId => { - store.dispatch(Actions.leaveRoom(roomId)); - }, - - addMessage: (roomId, message) => { - if (message.name) { - store.dispatch(reset('sayMessage')); - } - - store.dispatch(Actions.addMessage(roomId, message)); - }, - - updateGames: (roomId, games) => { - store.dispatch(Actions.updateGames(roomId, games)); - }, - - userJoined: (roomId, user) => { - store.dispatch(Actions.userJoined(roomId, user)); - }, - - userLeft: (roomId, name) => { - store.dispatch(Actions.userLeft(roomId, name)); - }, - - sortGames: (roomId, field, order) => { - store.dispatch(Actions.sortGames(roomId, field, order)); - }, - - removeMessages: (roomId, name, amount) => { - store.dispatch(Actions.removeMessages(roomId, name, amount)); - }, - - gameCreated: (roomId) => { - store.dispatch(Actions.gameCreated(roomId)); - }, - - joinedGame: (roomId, gameId) => { - store.dispatch(Actions.joinedGame(roomId, gameId)); - } -} diff --git a/webclient/src/store/rooms/rooms.interfaces.tsx b/webclient/src/store/rooms/rooms.interfaces.tsx deleted file mode 100644 index c7b90ac84..000000000 --- a/webclient/src/store/rooms/rooms.interfaces.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { GameSortField, Room, Game, SortBy, UserSortField } from 'types'; - -export interface RoomsState { - rooms: RoomsStateRooms; - games: RoomsStateGames; - joinedRoomIds: JoinedRooms; - joinedGameIds: JoinedGames; - messages: RoomsStateMessages; - sortGamesBy: RoomsStateSortGamesBy; - sortUsersBy: RoomsStateSortUsersBy; -} - -export interface RoomsStateRooms { - [roomId: number]: Room; -} - -export interface RoomsStateGames { - [roomId: number]: { - [gameId: number]: Game; - }; -} - -export interface JoinedRooms { - [roomId: number]: boolean; -} - -export interface JoinedGames { - [roomId: number]: { - [gameId: number]: boolean; - }; -} - -export interface RoomsStateMessages { - [roomId: number]: Message[]; -} - -export interface RoomsStateSortGamesBy extends SortBy { - field: GameSortField -} - -export interface RoomsStateSortUsersBy extends SortBy { - field: UserSortField -} - -export interface Message { - message: string; - messageType: number; - timeReceived: number; - timeOf?: number; -} diff --git a/webclient/src/store/rooms/rooms.reducer.tsx b/webclient/src/store/rooms/rooms.reducer.tsx deleted file mode 100644 index 5898c7150..000000000 --- a/webclient/src/store/rooms/rooms.reducer.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import * as _ from 'lodash'; - -import { GameSortField, UserSortField, SortDirection } from 'types'; - -import { SortUtil } from '../common'; - -import { RoomsState } from './rooms.interfaces' -import { MAX_ROOM_MESSAGES, Types } from './rooms.types'; - -const initialState: RoomsState = { - rooms: {}, - games: {}, - joinedRoomIds: {}, - joinedGameIds: {}, - messages: {}, - sortGamesBy: { - field: GameSortField.START_TIME, - order: SortDirection.DESC - }, - sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC - } -}; - -export const roomsReducer = (state = initialState, action: any) => { - switch (action.type) { - case Types.CLEAR_STORE: { - return { - ...initialState - }; - } - - case Types.UPDATE_ROOMS: { - const rooms = { - ...state.rooms - }; - - // Server does not send everything on updates - _.each(action.rooms, (room, order) => { - const { roomId } = room; - const existing = rooms[roomId] || {}; - - const update = { ...room }; - delete update.gameList; - delete update.gametypeList; - delete update.userList; - - rooms[roomId] = { - ...existing, - ...update, - order - }; - }); - - return { ...state, rooms }; - } - - case Types.JOIN_ROOM: { - const { roomInfo } = action; - const { joinedRoomIds, rooms, sortGamesBy, sortUsersBy } = state; - - const { roomId } = roomInfo; - - const gameList = [ - ...roomInfo.gameList - ]; - - const userList = [ - ...roomInfo.userList - ]; - - SortUtil.sortByField(gameList, sortGamesBy); - SortUtil.sortUsersByField(userList, sortUsersBy); - - return { - ...state, - - rooms: { - ...rooms, - [roomId]: { - ...roomInfo, - gameList, - userList - } - }, - - joinedRoomIds: { - ...joinedRoomIds, - [roomId]: true - }, - } - } - - case Types.LEAVE_ROOM: { - const { roomId } = action; - const { joinedRoomIds, messages } = state; - - const _joined = { - ...joinedRoomIds - }; - - const _messages = { - ...messages - }; - - delete _joined[roomId]; - delete _messages[roomId]; - - return { - ...state, - - joinedRoomIds: _joined, - messages: _messages, - } - } - - case Types.ADD_MESSAGE: { - const { roomId, message } = action; - const { messages } = state; - - let roomMessages = [...(messages[roomId] || [])]; - - if (roomMessages.length === MAX_ROOM_MESSAGES) { - roomMessages.shift(); - } - - message.timeReceived = new Date().getTime(); - roomMessages.push(message); - - return { - ...state, - messages: { - ...messages, - - [roomId]: [ - ...roomMessages - ] - } - } - } - // @TODO improve this reducer, likely by improving the store model - - case Types.UPDATE_GAMES: { - const { roomId, games } = action; - const { rooms, sortGamesBy } = state; - const room = rooms[roomId]; - - if (!room) { - return { ...state }; - } - - // Create map of games with update objects - const toUpdate = games.reduce((map, game) => { - map[game.gameId] = game; - return map; - }, {}); - - const gameUpdates = room.gameList - // filter out closed games and remove from update map - .filter(game => { - const gameUpdate = toUpdate[game.gameId]; - const closedGame = gameUpdate && gameUpdate.closed; - - if (closedGame) { - delete toUpdate[game.gameId]; - } - - return !closedGame; - }) - .map(game => { - const gameUpdate = toUpdate[game.gameId]; - - if (gameUpdate) { - delete toUpdate[game.gameId]; - - return { - ...game, - ...gameUpdate - }; - } - - return game; - }); - - // Push new games to end of list - if (_.size(toUpdate)) { - _.each(toUpdate, game => gameUpdates.push(game)); - } - - const gameList = [...gameUpdates]; - - SortUtil.sortByField(gameList, sortGamesBy); - - return { - ...state, - rooms: { - ...rooms, - [roomId]: { - ...room, - gameList - } - } - } - } - - case Types.USER_JOINED: { - const { roomId, user } = action; - const { rooms, sortUsersBy } = state; - - const room = { ...rooms[roomId] }; - - const userList = [ - ...room.userList, - user - ]; - - SortUtil.sortUsersByField(userList, sortUsersBy); - - return { - ...state, - rooms: { - ...rooms, - [roomId]: { - ...room, - userList - } - } - }; - } - - case Types.USER_LEFT: { - const { roomId, name } = action; - const { rooms } = state; - - const room = { ...rooms[roomId] }; - const userList = room.userList.filter(user => user.name !== name); - - return { - ...state, - rooms: { - ...rooms, - [roomId]: { - ...room, - userList - } - } - }; - } - - case Types.SORT_GAMES: { - const { field, order, roomId } = action; - const { rooms } = state; - - const gameList = [...rooms[roomId].gameList]; - - const sortGamesBy = { - field, order - }; - - SortUtil.sortByField(gameList, sortGamesBy); - - return { - ...state, - - rooms: { - ...rooms, - [roomId]: { - ...rooms[roomId], - gameList - } - }, - - sortGamesBy - } - } - - case Types.REMOVE_MESSAGES: { - const { name, amount, roomId } = action; - const { messages } = state; - let amountRemoved = 0; - - return { - ...state, - messages: { - ...messages, - [roomId]: messages[roomId] - .reverse() - .filter(({ message }) => { - if (amount === amountRemoved) { - return true; - } - - const keep = message.indexOf(`${name}:`) !== 0; - - if (!keep) { - amountRemoved++; - } - - return keep; - }) - .reverse() - } - } - } - - case Types.JOINED_GAME: { - const { gameId, roomId } = action; - const { joinedGameIds } = state; - - return { - ...state, - joinedGameIds: { - ...joinedGameIds, - [roomId]: { - ...joinedGameIds[roomId], - [gameId]: true, - } - } - } - } - - default: - return state; - } -} diff --git a/webclient/src/store/rooms/rooms.selectors.tsx b/webclient/src/store/rooms/rooms.selectors.tsx deleted file mode 100644 index e1e7ec818..000000000 --- a/webclient/src/store/rooms/rooms.selectors.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as _ from 'lodash'; -import { RoomsState } from './rooms.interfaces'; - -interface State { - rooms: RoomsState -} - -export const Selectors = { - getRooms: ({ rooms }: State) => rooms.rooms, - getGames: ({ rooms }: State) => rooms.games, - getRoom: ({ rooms }: State, id: number) => - _.find(rooms.rooms, ({ roomId }) => roomId === id), - getJoinedRoomIds: ({ rooms }: State) => rooms.joinedRoomIds, - getJoinedGameIds: ({ rooms }: State) => rooms.joinedGameIds, - getMessages: ({ rooms }: State) => rooms.messages, - getSortGamesBy: ({ rooms: { sortGamesBy } }: State) => sortGamesBy, - getSortUsersBy: ({ rooms: { sortUsersBy } }: State) => sortUsersBy, - - getJoinedRooms: (state: State) => { - const joined = Selectors.getJoinedRoomIds(state); - return _.filter(Selectors.getRooms(state), room => joined[room.roomId]); - }, - - getJoinedGames: (state: State, roomId: number) => { - const joined = Selectors.getJoinedGameIds(state)[roomId]; - return _.filter(Selectors.getGames(state)[roomId], game => joined[game.gameId]); - }, - - getRoomMessages: (state: State, roomId: number) => Selectors.getMessages(state)[roomId], - getRoomGames: (state: State, roomId: number) => Selectors.getRooms(state)[roomId].gameList, - getRoomUsers: (state: State, roomId: number) => Selectors.getRooms(state)[roomId].userList - -} - diff --git a/webclient/src/store/rooms/rooms.types.tsx b/webclient/src/store/rooms/rooms.types.tsx deleted file mode 100644 index 0b07eadd1..000000000 --- a/webclient/src/store/rooms/rooms.types.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export const Types = { - CLEAR_STORE: '[Rooms] Clear Store', - UPDATE_ROOMS: '[Rooms] Update Rooms', - JOIN_ROOM: '[Rooms] Join Room', - LEAVE_ROOM: '[Rooms] Leave Room', - ADD_MESSAGE: '[Rooms] Add Message', - UPDATE_GAMES: '[Rooms] Update Games', - USER_JOINED: '[Rooms] User Joined', - USER_LEFT: '[Rooms] User Left', - SORT_GAMES: '[Rooms] Sort Games', - REMOVE_MESSAGES: '[Rooms] Remove Messages', - GAME_CREATED: '[Rooms] Game Created', - JOINED_GAME: '[Rooms] Joined Game', -}; - -export const MAX_ROOM_MESSAGES = 1000; diff --git a/webclient/src/store/rootReducer.ts b/webclient/src/store/rootReducer.ts deleted file mode 100644 index 0f39d7e37..000000000 --- a/webclient/src/store/rootReducer.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { combineReducers } from 'redux'; - -import { roomsReducer } from './rooms'; -import { serverReducer } from './server'; -import { reducer as formReducer } from 'redux-form' -import { actionReducer } from './actions' - -export default combineReducers({ - rooms: roomsReducer, - server: serverReducer, - - form: formReducer, - action: actionReducer -}); diff --git a/webclient/src/store/server/index.ts b/webclient/src/store/server/index.ts deleted file mode 100644 index f2a9c5dca..000000000 --- a/webclient/src/store/server/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { Actions } from './server.actions'; -export { Dispatch } from './server.dispatch'; -export * from './server.reducer'; -export { Selectors } from './server.selectors'; -export * from './server.types'; diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts deleted file mode 100644 index 8af5adb75..000000000 --- a/webclient/src/store/server/server.actions.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { DeckList, DeckStorageTreeItem, ReplayMatch, WebSocketConnectOptions } from 'types'; -import { Types } from './server.types'; - -export const Actions = { - initialized: () => ({ - type: Types.INITIALIZED - }), - clearStore: () => ({ - type: Types.CLEAR_STORE - }), - loginSuccessful: (options: WebSocketConnectOptions) => ({ - type: Types.LOGIN_SUCCESSFUL, - options - }), - loginFailed: () => ({ - type: Types.LOGIN_FAILED, - }), - connectionClosed: reason => ({ - type: Types.CONNECTION_CLOSED, - reason - }), - connectionFailed: () => ({ - type: Types.CONNECTION_FAILED, - }), - testConnectionSuccessful: () => ({ - type: Types.TEST_CONNECTION_SUCCESSFUL, - }), - testConnectionFailed: () => ({ - type: Types.TEST_CONNECTION_FAILED, - }), - serverMessage: message => ({ - type: Types.SERVER_MESSAGE, - message - }), - updateBuddyList: buddyList => ({ - type: Types.UPDATE_BUDDY_LIST, - buddyList - }), - addToBuddyList: user => ({ - type: Types.ADD_TO_BUDDY_LIST, - user - }), - removeFromBuddyList: userName => ({ - type: Types.REMOVE_FROM_BUDDY_LIST, - userName - }), - updateIgnoreList: ignoreList => ({ - type: Types.UPDATE_IGNORE_LIST, - ignoreList - }), - addToIgnoreList: user => ({ - type: Types.ADD_TO_IGNORE_LIST, - user - }), - removeFromIgnoreList: userName => ({ - type: Types.REMOVE_FROM_IGNORE_LIST, - userName - }), - updateInfo: info => ({ - type: Types.UPDATE_INFO, - info - }), - updateStatus: status => ({ - type: Types.UPDATE_STATUS, - status - }), - updateUser: user => ({ - type: Types.UPDATE_USER, - user - }), - updateUsers: users => ({ - type: Types.UPDATE_USERS, - users - }), - userJoined: user => ({ - type: Types.USER_JOINED, - user - }), - userLeft: name => ({ - type: Types.USER_LEFT, - name - }), - viewLogs: logs => ({ - type: Types.VIEW_LOGS, - logs - }), - clearLogs: () => ({ - type: Types.CLEAR_LOGS, - }), - registrationRequiresEmail: () => ({ - type: Types.REGISTRATION_REQUIRES_EMAIL, - }), - registrationSuccess: () => ({ - type: Types.REGISTRATION_SUCCES, - }), - registrationFailed: (error) => ({ - type: Types.REGISTRATION_FAILED, - error - }), - registrationEmailError: (error) => ({ - type: Types.REGISTRATION_EMAIL_ERROR, - error - }), - registrationPasswordError: (error) => ({ - type: Types.REGISTRATION_PASSWORD_ERROR, - error - }), - registrationUserNameError: (error) => ({ - type: Types.REGISTRATION_USERNAME_ERROR, - error - }), - accountAwaitingActivation: (options: WebSocketConnectOptions) => ({ - type: Types.ACCOUNT_AWAITING_ACTIVATION, - options - }), - accountActivationSuccess: () => ({ - type: Types.ACCOUNT_ACTIVATION_SUCCESS, - }), - accountActivationFailed: () => ({ - type: Types.ACCOUNT_ACTIVATION_FAILED, - }), - resetPassword: () => ({ - type: Types.RESET_PASSWORD_REQUESTED, - }), - resetPasswordFailed: () => ({ - type: Types.RESET_PASSWORD_FAILED, - }), - resetPasswordChallenge: () => ({ - type: Types.RESET_PASSWORD_CHALLENGE, - }), - resetPasswordSuccess: () => ({ - type: Types.RESET_PASSWORD_SUCCESS, - }), - adjustMod: (userName, shouldBeMod, shouldBeJudge) => ({ - type: Types.ADJUST_MOD, - userName, - shouldBeMod, - shouldBeJudge, - }), - reloadConfig: () => ({ - type: Types.RELOAD_CONFIG, - }), - shutdownServer: () => ({ - type: Types.SHUTDOWN_SERVER, - }), - updateServerMessage: () => ({ - type: Types.UPDATE_SERVER_MESSAGE, - }), - accountPasswordChange: () => ({ - type: Types.ACCOUNT_PASSWORD_CHANGE, - }), - accountEditChanged: (user) => ({ - type: Types.ACCOUNT_EDIT_CHANGED, - user, - }), - accountImageChanged: (user) => ({ - type: Types.ACCOUNT_IMAGE_CHANGED, - user, - }), - directMessageSent: (userName, message) => ({ - type: Types.DIRECT_MESSAGE_SENT, - userName, - message, - }), - getUserInfo: (userInfo) => ({ - type: Types.GET_USER_INFO, - userInfo, - }), - notifyUser: (notification) => ({ - type: Types.NOTIFY_USER, - notification, - }), - serverShutdown: (data) => ({ - type: Types.SERVER_SHUTDOWN, - data, - }), - userMessage: (messageData) => ({ - type: Types.USER_MESSAGE, - messageData, - }), - addToList: (list, userName) => ({ - type: Types.ADD_TO_LIST, - list, - userName, - }), - removeFromList: (list, userName) => ({ - type: Types.REMOVE_FROM_LIST, - list, - userName, - }), - banFromServer: (userName) => ({ - type: Types.BAN_FROM_SERVER, - userName, - }), - banHistory: (userName, banHistory) => ({ - type: Types.BAN_HISTORY, - userName, - banHistory, - }), - warnHistory: (userName, warnHistory) => ({ - type: Types.WARN_HISTORY, - userName, - warnHistory, - }), - warnListOptions: (warnList) => ({ - type: Types.WARN_LIST_OPTIONS, - warnList, - }), - warnUser: (userName) => ({ - type: Types.WARN_USER, - userName, - }), - grantReplayAccess: (replayId: number, moderatorName: string) => ({ - type: Types.GRANT_REPLAY_ACCESS, - replayId, - moderatorName, - }), - forceActivateUser: (usernameToActivate: string, moderatorName: string) => ({ - type: Types.FORCE_ACTIVATE_USER, - usernameToActivate, - moderatorName, - }), - getAdminNotes: (userName: string, notes: string) => ({ - type: Types.GET_ADMIN_NOTES, - userName, - notes, - }), - updateAdminNotes: (userName: string, notes: string) => ({ - type: Types.UPDATE_ADMIN_NOTES, - userName, - notes, - }), - replayList: (matchList: ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }), - replayAdded: (matchInfo: ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }), - replayModifyMatch: (gameId: number, doNotHide: boolean) => ({ type: Types.REPLAY_MODIFY_MATCH, gameId, doNotHide }), - replayDeleteMatch: (gameId: number) => ({ type: Types.REPLAY_DELETE_MATCH, gameId }), - backendDecks: (deckList: DeckList) => ({ type: Types.BACKEND_DECKS, deckList }), - deckNewDir: (path: string, dirName: string) => ({ type: Types.DECK_NEW_DIR, path, dirName }), - deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }), - deckUpload: (path: string, treeItem: DeckStorageTreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), - deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }), -} diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts deleted file mode 100644 index 3d3b6d360..000000000 --- a/webclient/src/store/server/server.dispatch.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { reset } from 'redux-form'; -import { Actions } from './server.actions'; -import { store } from 'store'; -import { DeckList, DeckStorageTreeItem, ReplayMatch, WebSocketConnectOptions } from 'types'; - -export const Dispatch = { - initialized: () => { - store.dispatch(Actions.initialized()); - }, - clearStore: () => { - store.dispatch(Actions.clearStore()); - }, - loginSuccessful: options => { - store.dispatch(Actions.loginSuccessful(options)); - }, - loginFailed: () => { - store.dispatch(Actions.loginFailed()); - }, - connectionClosed: reason => { - store.dispatch(Actions.connectionClosed(reason)); - }, - connectionFailed: () => { - store.dispatch(Actions.connectionFailed()); - }, - testConnectionSuccessful: () => { - store.dispatch(Actions.testConnectionSuccessful()); - }, - testConnectionFailed: () => { - store.dispatch(Actions.testConnectionFailed()); - }, - updateBuddyList: buddyList => { - store.dispatch(Actions.updateBuddyList(buddyList)); - }, - addToBuddyList: user => { - store.dispatch(reset('addToBuddies')); - store.dispatch(Actions.addToBuddyList(user)); - }, - removeFromBuddyList: userName => { - store.dispatch(Actions.removeFromBuddyList(userName)); - }, - updateIgnoreList: ignoreList => { - store.dispatch(Actions.updateIgnoreList(ignoreList)); - }, - addToIgnoreList: user => { - store.dispatch(reset('addToIgnore')); - store.dispatch(Actions.addToIgnoreList(user)); - }, - removeFromIgnoreList: userName => { - store.dispatch(Actions.removeFromIgnoreList(userName)); - }, - updateInfo: (name, version) => { - store.dispatch(Actions.updateInfo({ - name, - version - })); - }, - updateStatus: (state, description) => { - store.dispatch(Actions.updateStatus({ - state, - description - })); - }, - updateUser: user => { - store.dispatch(Actions.updateUser(user)); - }, - updateUsers: users => { - store.dispatch(Actions.updateUsers(users)); - }, - userJoined: user => { - store.dispatch(Actions.userJoined(user)); - }, - userLeft: name => { - store.dispatch(Actions.userLeft(name)); - }, - viewLogs: name => { - store.dispatch(Actions.viewLogs(name)); - }, - clearLogs: () => { - store.dispatch(Actions.clearLogs()); - }, - serverMessage: message => { - store.dispatch(Actions.serverMessage(message)); - }, - registrationRequiresEmail: () => { - store.dispatch(Actions.registrationRequiresEmail()); - }, - registrationSuccess: () => { - store.dispatch(Actions.registrationSuccess()) - }, - registrationFailed: (error) => { - store.dispatch(Actions.registrationFailed(error)); - }, - registrationEmailError: (error) => { - store.dispatch(Actions.registrationEmailError(error)); - }, - registrationPasswordError: (error) => { - store.dispatch(Actions.registrationPasswordError(error)); - }, - registrationUserNameError: (error) => { - store.dispatch(Actions.registrationUserNameError(error)); - }, - accountAwaitingActivation: (options: WebSocketConnectOptions) => { - store.dispatch(Actions.accountAwaitingActivation(options)); - }, - accountActivationSuccess: () => { - store.dispatch(Actions.accountActivationSuccess()); - }, - accountActivationFailed: () => { - store.dispatch(Actions.accountActivationFailed()); - }, - resetPassword: () => { - store.dispatch(Actions.resetPassword()); - }, - resetPasswordFailed: () => { - store.dispatch(Actions.resetPasswordFailed()); - }, - resetPasswordChallenge: () => { - store.dispatch(Actions.resetPasswordChallenge()); - }, - resetPasswordSuccess: () => { - store.dispatch(Actions.resetPasswordSuccess()); - }, - adjustMod: (userName, shouldBeMod, shouldBeJudge) => { - store.dispatch(Actions.adjustMod(userName, shouldBeMod, shouldBeJudge)); - }, - reloadConfig: () => { - store.dispatch(Actions.reloadConfig()); - }, - shutdownServer: () => { - store.dispatch(Actions.shutdownServer()); - }, - updateServerMessage: () => { - store.dispatch(Actions.updateServerMessage()); - }, - accountPasswordChange: () => { - store.dispatch(Actions.accountPasswordChange()); - }, - accountEditChanged: (user) => { - store.dispatch(Actions.accountEditChanged(user)); - }, - accountImageChanged: (user) => { - store.dispatch(Actions.accountImageChanged(user)); - }, - directMessageSent: (userName, message) => { - store.dispatch(Actions.directMessageSent(userName, message)); - }, - getUserInfo: (userInfo) => { - store.dispatch(Actions.getUserInfo(userInfo)); - }, - notifyUser: (notification) => { - store.dispatch(Actions.notifyUser(notification)) - }, - serverShutdown: (data) => { - store.dispatch(Actions.serverShutdown(data)) - }, - userMessage: (messageData) => { - store.dispatch(Actions.userMessage(messageData)) - }, - addToList: (list, userName) => { - store.dispatch(Actions.addToList(list, userName)) - }, - removeFromList: (list, userName) => { - store.dispatch(Actions.removeFromList(list, userName)) - }, - banFromServer: (userName) => { - store.dispatch(Actions.banFromServer(userName)); - }, - banHistory: (userName, banHistory) => { - store.dispatch(Actions.banHistory(userName, banHistory)) - }, - warnHistory: (userName, warnHistory) => { - store.dispatch(Actions.warnHistory(userName, warnHistory)) - }, - warnListOptions: (warnList) => { - store.dispatch(Actions.warnListOptions(warnList)) - }, - warnUser: (userName) => { - store.dispatch(Actions.warnUser(userName)) - }, - grantReplayAccess: (replayId: number, moderatorName: string) => { - store.dispatch(Actions.grantReplayAccess(replayId, moderatorName)); - }, - forceActivateUser: (usernameToActivate: string, moderatorName: string) => { - store.dispatch(Actions.forceActivateUser(usernameToActivate, moderatorName)); - }, - getAdminNotes: (userName: string, notes: string) => { - store.dispatch(Actions.getAdminNotes(userName, notes)); - }, - updateAdminNotes: (userName: string, notes: string) => { - store.dispatch(Actions.updateAdminNotes(userName, notes)); - }, - replayList: (matchList: ReplayMatch[]) => { - store.dispatch(Actions.replayList(matchList)); - }, - replayAdded: (matchInfo: ReplayMatch) => { - store.dispatch(Actions.replayAdded(matchInfo)); - }, - replayModifyMatch: (gameId: number, doNotHide: boolean) => { - store.dispatch(Actions.replayModifyMatch(gameId, doNotHide)); - }, - replayDeleteMatch: (gameId: number) => { - store.dispatch(Actions.replayDeleteMatch(gameId)); - }, - backendDecks: (deckList: DeckList) => { - store.dispatch(Actions.backendDecks(deckList)); - }, - deckNewDir: (path: string, dirName: string) => { - store.dispatch(Actions.deckNewDir(path, dirName)); - }, - deckDelDir: (path: string) => { - store.dispatch(Actions.deckDelDir(path)); - }, - deckUpload: (path: string, treeItem: DeckStorageTreeItem) => { - store.dispatch(Actions.deckUpload(path, treeItem)); - }, - deckDelete: (deckId: number) => { - store.dispatch(Actions.deckDelete(deckId)); - }, -} diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts deleted file mode 100644 index 97e9d99da..000000000 --- a/webclient/src/store/server/server.interfaces.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - WarnHistoryItem, BanHistoryItem, DeckList, LogItem, ReplayMatch, SortBy, User, UserSortField, WebSocketConnectOptions, WarnListItem -} from 'types'; -import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; - -export interface ServerConnectParams { - host: string; - port: string; - userName: string; - password: string; -} - -export interface ServerRegisterParams { - host: string; - port: string; - userName: string; - password: string; - email: string; - country: string; - realName: string; -} - -export interface RequestPasswordSaltParams { - userName: string; -} - -export interface ForgotPasswordParams { - userName: string; -} - -export interface ForgotPasswordChallengeParams extends ForgotPasswordParams { - email: string; -} - -export interface ForgotPasswordResetParams extends ForgotPasswordParams { - token: string; - newPassword: string; -} - -export interface AccountActivationParams extends ServerRegisterParams { - token: string; -} - -export interface ServerState { - initialized: boolean; - buddyList: User[]; - ignoreList: User[]; - info: ServerStateInfo; - status: ServerStateStatus; - logs: ServerStateLogs; - user: User; - users: User[]; - sortUsersBy: ServerStateSortUsersBy; - connectOptions: WebSocketConnectOptions; - messages: { - [userName: string]: UserMessageData[]; - } - userInfo: { - [userName: string]: User; - } - notifications: NotifyUserData[]; - serverShutdown: ServerShutdownData; - banUser: string; - banHistory: { - [userName: string]: BanHistoryItem[]; - }; - warnHistory: { - [userName: string]: WarnHistoryItem[]; - }; - warnListOptions: WarnListItem[]; - warnUser: string; - adminNotes: { [userName: string]: string }; - replays: ReplayMatch[]; - backendDecks: DeckList | null; -} - -export interface ServerStateStatus { - description: string; - state: number; -} - -export interface ServerStateInfo { - message: string; - name: string; - version: string; -} - -export interface ServerStateLogs { - room: LogItem[]; - game: LogItem[]; - chat: LogItem[]; -} - -export interface ServerStateSortUsersBy extends SortBy { - field: UserSortField -} diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts deleted file mode 100644 index a63418eeb..000000000 --- a/webclient/src/store/server/server.reducer.ts +++ /dev/null @@ -1,481 +0,0 @@ -import { DeckStorageFolder, DeckStorageTreeItem, SortDirection, StatusEnum, UserLevelFlag, UserSortField } from 'types'; - -import { SortUtil } from '../common'; - -import { ServerState } from './server.interfaces' -import { Types } from './server.types'; - -function splitPath(path: string): string[] { - return path ? path.split('/') : []; -} - -function insertAtPath(folder: DeckStorageFolder, pathSegments: string[], item: DeckStorageTreeItem): DeckStorageFolder { - if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { - return { items: [...folder.items, item] }; - } - const [head, ...tail] = pathSegments; - const match = folder.items.find(child => child.name === head && child.folder); - if (match) { - return { - items: folder.items.map(child => - child === match - ? { ...child, folder: insertAtPath(child.folder!, tail, item) } - : child - ), - }; - } - const created: DeckStorageTreeItem = { id: 0, name: head, file: null, folder: insertAtPath({ items: [] }, tail, item) }; - return { items: [...folder.items, created] }; -} - -function removeById(folder: DeckStorageFolder, id: number): DeckStorageFolder { - return { - items: folder.items - .filter(item => item.id !== id) - .map(item => - item.folder ? { ...item, folder: removeById(item.folder, id) } : item - ), - }; -} - -function removeByPath(folder: DeckStorageFolder, pathSegments: string[]): DeckStorageFolder { - if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { - return folder; - } - const [head, ...tail] = pathSegments; - if (tail.length === 0) { - return { items: folder.items.filter(item => !(item.name === head && item.folder !== null)) }; - } - return { - items: folder.items.map(item => - item.name === head && item.folder - ? { ...item, folder: removeByPath(item.folder, tail) } - : item - ), - }; -} - -const initialState: ServerState = { - initialized: false, - buddyList: [], - ignoreList: [], - - status: { - state: StatusEnum.DISCONNECTED, - description: null - }, - info: { - message: null, - name: null, - version: null - }, - logs: { - room: [], - game: [], - chat: [] - }, - user: null, - users: [], - sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC - }, - connectOptions: {}, - messages: {}, - userInfo: {}, - notifications: [], - serverShutdown: null, - banUser: '', - banHistory: {}, - warnHistory: {}, - warnListOptions: [], - warnUser: '', - adminNotes: {}, - replays: [], - backendDecks: null, -}; - -export const serverReducer = (state = initialState, action: any) => { - switch (action.type) { - case Types.INITIALIZED: { - return { - ...initialState, - initialized: true - } - } - case Types.ACCOUNT_AWAITING_ACTIVATION: { - return { - ...state, - connectOptions: { - ...action.options - } - } - } - case Types.ACCOUNT_ACTIVATION_FAILED: - case Types.ACCOUNT_ACTIVATION_SUCCESS: { - return { - ...state, - connectOptions: {} - } - } - case Types.CLEAR_STORE: { - return { - ...initialState, - status: { - ...state.status - } - } - } - case Types.SERVER_MESSAGE: { - const { message } = action; - const { info } = state; - - return { - ...state, - info: { ...info, message } - } - } - case Types.UPDATE_BUDDY_LIST: { - const { buddyList } = action; - const { sortUsersBy } = state; - - SortUtil.sortUsersByField(buddyList, sortUsersBy); - - return { - ...state, - buddyList: [ - ...buddyList - ] - }; - } - case Types.ADD_TO_BUDDY_LIST: { - const { user } = action; - const { sortUsersBy } = state; - - const buddyList = [...state.buddyList]; - - buddyList.push(user); - SortUtil.sortUsersByField(buddyList, sortUsersBy); - - return { - ...state, - buddyList - }; - } - case Types.REMOVE_FROM_BUDDY_LIST: { - const { userName } = action; - const buddyList = state.buddyList.filter(user => user.name !== userName); - - return { - ...state, - buddyList - }; - } - case Types.UPDATE_IGNORE_LIST: { - const { ignoreList } = action; - const { sortUsersBy } = state; - - SortUtil.sortUsersByField(ignoreList, sortUsersBy); - - return { - ...state, - ignoreList: [ - ...ignoreList - ] - }; - } - case Types.ADD_TO_IGNORE_LIST: { - const { user } = action; - const { sortUsersBy } = state; - - const ignoreList = [...state.ignoreList]; - - ignoreList.push(user); - SortUtil.sortUsersByField(ignoreList, sortUsersBy); - - return { - ...state, - ignoreList - }; - } - case Types.REMOVE_FROM_IGNORE_LIST: { - const { userName } = action; - const ignoreList = state.ignoreList.filter(user => user.name !== userName); - - return { - ...state, - ignoreList - }; - } - case Types.UPDATE_INFO: { - const { name, version } = action.info; - const { info } = state; - - return { - ...state, - info: { ...info, name, version } - } - } - case Types.UPDATE_STATUS: { - const { status } = action; - - return { - ...state, - status: { ...status } - } - } - case Types.UPDATE_USER: - case Types.ACCOUNT_EDIT_CHANGED: - case Types.ACCOUNT_IMAGE_CHANGED: { - const { user } = action; - - return { - ...state, - user: { - ...state.user, - ...user - } - } - } - case Types.UPDATE_USERS: { - const users = [...action.users]; - const { sortUsersBy } = state; - - - SortUtil.sortUsersByField(users, sortUsersBy); - - return { - ...state, - users - }; - } - case Types.USER_JOINED: { - const { sortUsersBy } = state; - - const users = [ - ...state.users, - { ...action.user } - ]; - - SortUtil.sortUsersByField(users, sortUsersBy); - - return { - ...state, - users - }; - } - case Types.USER_LEFT: { - const { name } = action; - const users = state.users.filter(user => user.name !== name); - - return { - ...state, - users - }; - } - case Types.VIEW_LOGS: { - const { logs } = action; - - return { - ...state, - logs: { - ...logs - } - }; - } - case Types.CLEAR_LOGS: { - return { - ...state, - logs: { - ...initialState.logs - } - } - } - case Types.USER_MESSAGE: { - const { senderName, receiverName } = action.messageData; - const userName = state.user.name === senderName ? receiverName : senderName; - - return { - ...state, - messages: { - ...state.messages, - [userName]: [ - ...(state.messages[userName] ?? []), - action.messageData, - ], - } - }; - } - case Types.GET_USER_INFO: { - const { userInfo } = action; - - return { - ...state, - userInfo: { - ...state.userInfo, - [userInfo.name]: userInfo, - } - }; - } - case Types.NOTIFY_USER: { - const { notification } = action; - - return { - ...state, - notifications: [ - ...state.notifications, - notification - ] - }; - } - case Types.SERVER_SHUTDOWN: { - const { data } = action; - - return { - ...state, - serverShutdown: data, - }; - } - case Types.BAN_FROM_SERVER: { - const { userName } = action; - - return { - ...state, - banUser: userName, - }; - } - case Types.BAN_HISTORY: { - const { userName, banHistory } = action; - - return { - ...state, - banHistory: { - ...state.banHistory, - [userName]: banHistory, - } - }; - } - case Types.WARN_HISTORY: { - const { userName, warnHistory } = action; - - return { - ...state, - warnHistory: { - ...state.warnHistory, - [userName]: warnHistory, - } - }; - } - case Types.WARN_LIST_OPTIONS: { - const { warnList } = action; - - return { - ...state, - warnListOptions: warnList, - }; - } - case Types.WARN_USER: { - const { userName } = action; - return { - ...state, - warnUser: userName, - }; - } - case Types.GET_ADMIN_NOTES: - case Types.UPDATE_ADMIN_NOTES: { - const { userName, notes } = action; - return { - ...state, - adminNotes: { - ...state.adminNotes, - [userName]: notes, - } - }; - } - case Types.ADJUST_MOD: { - const { userName, shouldBeMod, shouldBeJudge } = action; - - return { - ...state, - users: state.users.map((user) => { - if (user.name !== userName) { - return user; - } - const judgeFlag = shouldBeJudge ? UserLevelFlag.IsJudge : UserLevelFlag.IsNothing; - const modFlag = shouldBeMod ? UserLevelFlag.IsModerator : UserLevelFlag.IsNothing; - return { - ...user, - userLevel: user.userLevel & (judgeFlag | modFlag) - } - }) - }; - } - case Types.REPLAY_LIST: { - return { ...state, replays: [...action.matchList] }; - } - case Types.REPLAY_ADDED: { - return { ...state, replays: [...state.replays, action.matchInfo] }; - } - case Types.REPLAY_MODIFY_MATCH: { - return { - ...state, - replays: state.replays.map(r => - r.gameId === action.gameId ? { ...r, doNotHide: action.doNotHide } : r - ), - }; - } - case Types.REPLAY_DELETE_MATCH: { - return { ...state, replays: state.replays.filter(r => r.gameId !== action.gameId) }; - } - case Types.BACKEND_DECKS: { - return { ...state, backendDecks: action.deckList }; - } - case Types.DECK_UPLOAD: { - if (!state.backendDecks) { - return state; - } - return { - ...state, - backendDecks: { - root: insertAtPath(state.backendDecks.root, splitPath(action.path), action.treeItem), - }, - }; - } - case Types.DECK_DELETE: { - if (!state.backendDecks) { - return state; - } - return { - ...state, - backendDecks: { - root: removeById(state.backendDecks.root, action.deckId), - }, - }; - } - case Types.DECK_NEW_DIR: { - if (!state.backendDecks) { - return state; - } - const newFolder: DeckStorageTreeItem = { id: 0, name: action.dirName, file: null, folder: { items: [] } }; - return { - ...state, - backendDecks: { - root: insertAtPath(state.backendDecks.root, splitPath(action.path), newFolder), - }, - }; - } - case Types.DECK_DEL_DIR: { - if (!state.backendDecks) { - return state; - } - return { - ...state, - backendDecks: { - root: removeByPath(state.backendDecks.root, splitPath(action.path)), - }, - }; - } - default: - return state; - } -} diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts deleted file mode 100644 index fa9f82297..000000000 --- a/webclient/src/store/server/server.selectors.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ServerState } from './server.interfaces'; - -interface State { - server: ServerState -} - -export const Selectors = { - getInitialized: ({ server }: State) => server.initialized, - getConnectOptions: ({ server }: State) => server.connectOptions, - getMessage: ({ server }: State) => server.info.message, - getName: ({ server }: State) => server.info.name, - getVersion: ({ server }: State) => server.info.version, - getDescription: ({ server }: State) => server.status.description, - getState: ({ server }: State) => server.status.state, - getUser: ({ server }: State) => server.user, - getUsers: ({ server }: State) => server.users, - getLogs: ({ server }: State) => server.logs, - getBuddyList: ({ server }: State) => server.buddyList, - getIgnoreList: ({ server }: State) => server.ignoreList, - getReplays: ({ server }: State) => server.replays, - getBackendDecks: ({ server }: State) => server.backendDecks, -} diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts deleted file mode 100644 index fb4249011..000000000 --- a/webclient/src/store/server/server.types.ts +++ /dev/null @@ -1,72 +0,0 @@ -export const Types = { - INITIALIZED: '[Server] Initialized', - CLEAR_STORE: '[Server] Clear Store', - LOGIN_SUCCESSFUL: '[Server] Login Successful', - LOGIN_FAILED: '[Server] Login Failed', - CONNECTION_CLOSED: '[Server] Connection Closed', - CONNECTION_FAILED: '[Server] Connection Failed', - TEST_CONNECTION_SUCCESSFUL: '[Server] Test Connection Successful', - TEST_CONNECTION_FAILED: '[Server] Test Connection Failed', - SERVER_MESSAGE: '[Server] Server Message', - UPDATE_BUDDY_LIST: '[Server] Update Buddy List', - ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List', - REMOVE_FROM_BUDDY_LIST: '[Server] Remove from Buddy List', - UPDATE_IGNORE_LIST: '[Server] Update Ignore List', - ADD_TO_IGNORE_LIST: '[Server] Add to Ignore List', - REMOVE_FROM_IGNORE_LIST: '[Server] Remove from Ignore List', - UPDATE_INFO: '[Server] Update Info', - UPDATE_STATUS: '[Server] Update Status', - UPDATE_USER: '[Server] Update User', - UPDATE_USERS: '[Server] Update Users', - USER_JOINED: '[Server] User Joined', - USER_LEFT: '[Server] User Left', - VIEW_LOGS: '[Server] View Logs', - CLEAR_LOGS: '[Server] Clear Logs', - REGISTRATION_REQUIRES_EMAIL: '[Server] Registration Requires Email', - REGISTRATION_SUCCES: '[Server] Registration Success', - REGISTRATION_FAILED: '[Server] Registration Failed', - REGISTRATION_EMAIL_ERROR: '[Server] Registration Email Error', - REGISTRATION_PASSWORD_ERROR: '[Server] Registration Password Error', - REGISTRATION_USERNAME_ERROR: '[Server] Registration Username Error', - ACCOUNT_AWAITING_ACTIVATION: '[Server] Account Awaiting Activation', - ACCOUNT_ACTIVATION_SUCCESS: '[Server] Account Activation Success', - ACCOUNT_ACTIVATION_FAILED: '[Server] Account Activation Failed', - RESET_PASSWORD_REQUESTED: '[Server] Reset Password Requested', - RESET_PASSWORD_FAILED: '[Server] Reset Password Failed', - RESET_PASSWORD_CHALLENGE: '[Server] Reset Password Challenge', - RESET_PASSWORD_SUCCESS: '[Server] Reset Password Success', - ADJUST_MOD: '[Server] Adjust Mod', - RELOAD_CONFIG: '[Server] Reload Config', - SHUTDOWN_SERVER: '[Server] Shutdown Server', - UPDATE_SERVER_MESSAGE: '[Server] Update Server Message', - ACCOUNT_PASSWORD_CHANGE: '[Server] Account Password Change', - ACCOUNT_EDIT_CHANGED: '[Server] Account Edit Changed', - ACCOUNT_IMAGE_CHANGED: '[Server] Account Image Changed', - DIRECT_MESSAGE_SENT: '[Server] Direct Message Sent', - GET_USER_INFO: '[Server] Get User Info', - NOTIFY_USER: '[Server] Notify User', - SERVER_SHUTDOWN: '[Server] Server Shutdown', - USER_MESSAGE: '[Server] User Message', - ADD_TO_LIST: '[Server] Add To List', - REMOVE_FROM_LIST: '[Server] Remove From List', - BAN_FROM_SERVER: '[Server] Ban From Server', - BAN_HISTORY: '[Server] Ban History', - WARN_HISTORY: '[Server] Warn History', - WARN_LIST_OPTIONS: '[Server] Warn List Options', - WARN_USER: '[Server] Warn User', - GRANT_REPLAY_ACCESS: '[Server] Grant Replay Access', - FORCE_ACTIVATE_USER: '[Server] Force Activate User', - GET_ADMIN_NOTES: '[Server] Get Admin Notes', - UPDATE_ADMIN_NOTES: '[Server] Update Admin Notes', - // Replay - REPLAY_LIST: '[Server] Replay List', - REPLAY_ADDED: '[Server] Replay Added', - REPLAY_MODIFY_MATCH: '[Server] Replay Modify Match', - REPLAY_DELETE_MATCH: '[Server] Replay Delete Match', - // Deck Storage - BACKEND_DECKS: '[Server] Backend Decks', - DECK_NEW_DIR: '[Server] Deck New Dir', - DECK_DEL_DIR: '[Server] Deck Del Dir', - DECK_UPLOAD: '[Server] Deck Upload', - DECK_DELETE: '[Server] Deck Delete', -}; diff --git a/webclient/src/store/store.ts b/webclient/src/store/store.ts deleted file mode 100644 index e6ef5df3a..000000000 --- a/webclient/src/store/store.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createStore, applyMiddleware } from 'redux'; -import thunk from 'redux-thunk'; -import rootReducer from './rootReducer'; - -const initialState = {}; - -const middleware: any = [thunk]; - -export const store = createStore(rootReducer, initialState, applyMiddleware(...middleware)); diff --git a/webclient/src/types/cards.ts b/webclient/src/types/cards.ts deleted file mode 100644 index b28bae203..000000000 --- a/webclient/src/types/cards.ts +++ /dev/null @@ -1,93 +0,0 @@ -export class Card { - artist: string; - availability: string[]; - borderColor: string; - colorIdentity: string[]; - colors: string[]; - convertedManaCost: number; - edhrecRank: number; - flavorText: string; - identifiers: { - cardKingdomId: string; - mcmId: string; - mcmMetaId: string; - mtgjsonV4Id: string; - multiverseId: string; - scryfallId: string; - scryfallIllustrationId: string; - scryfallOracleId: string; - tcgplayerProductId: string; - }; - isOnlineOnly: boolean; - layout: string; - legalities: { - brawl: string; - commander: string; - duel: string; - future: string; - gladiator: string; - historic: string; - legacy: string; - modern: string; - pauper: string; - penny: string; - pioneer: string; - premodern: string; - standard: string; - vintage: string; - }; - manaCost: string; - name: string; - originalText: string; - originalType: string; - power: string; - printings: string[]; - rarity: string; - rulings: { - date: string; - text: string; - }[]; - side: string; - setCode: string; - subtypes: string[]; - supertypes: string[]; - text: string; - toughness: string; - type: string; - types: string[]; - uuid: string; - variations: string[]; -} - -export class Set { - baseSetSize: number; - block: string; - cards: string[]; - code: string; - isOnlineOnly: boolean; - name: string; - releaseDate: string; - totalSetSize: number; - type: string; -} - -export class Token { - name: { value: string }; - prop: { - value: { - cmc: { value: string; }; - colors: { value: string; }; - maintype: { value: string; }; - pt: { value: string; }; - type: { value: string; }; - }; - }; - related: { value: string; }[]; - 'reverse-related': { value: string; }[]; - set: { - value: string; - picURL: string; - }[]; - tablerow: { value: string; }; - text: { value: string; }; -} diff --git a/webclient/src/types/constants.spec.ts b/webclient/src/types/constants.spec.ts deleted file mode 100644 index f95a16eaa..000000000 --- a/webclient/src/types/constants.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - URL_REGEX, - MESSAGE_SENDER_REGEX, - MENTION_REGEX, - CARD_CALLOUT_REGEX, - CALLOUT_BOUNDARY_REGEX, -} from './constants'; - -describe('RegEx', () => { - describe('URL_REGEX', () => { - it('should match and capture whole url in main capture group', () => { - const test = [ - 'http://example.com', - 'https://example.com', - 'https://www.example.com', - ]; - - test.forEach(str => { - const match = str.match(URL_REGEX); - - expect(match).toBeDefined(); - expect(match[0]).toBe(str); - }); - }); - - it('should not match bad urls', () => { - const test = [ - 'htt://example.com', - 'https:/example.com', - 'https//www.example.com', - 'www.example.com', - 'example.com', - ]; - - test.forEach(str => - expect(str.match(URL_REGEX)).toBe(null) - ); - }); - }); - - describe('MESSAGE_SENDER_REGEX', () => { - it('should match and capture sender name in second capture group', () => { - const sender = 'sender'; - const match = `${sender}: message`.match(MESSAGE_SENDER_REGEX); - - expect(match).toBeDefined(); - expect(match[1]).toBe(sender); - }); - - it('should not match if spaces before :', () => { - const test = [ - ' sender: message', - 'sender : message', - ' sender : message', - ]; - - test.forEach(str => - expect(str.match(URL_REGEX)).toBe(null) - ); - }); - }); - - describe('MENTION_REGEX', () => { - it('should match and capture user mentions in second capture group', () => { - expect('@mention'.match(MENTION_REGEX)[0]).toBe('@mention'); - expect('@mention '.match(MENTION_REGEX)[0]).toBe('@mention'); - expect(' @mention'.match(MENTION_REGEX)[0]).toBe(' @mention'); - expect(' @mention '.match(MENTION_REGEX)[0]).toBe(' @mention'); - expect('leading @mention'.match(MENTION_REGEX)[0]).toBe(' @mention'); - expect('leading @mention trailing'.match(MENTION_REGEX)[0]).toBe(' @mention'); - expect('@mention trailing'.match(MENTION_REGEX)[0]).toBe('@mention'); - }); - - it('should not match preceded by character', () => { - const test = [ - 'leading@mention', - ]; - - test.forEach(str => - expect(str.match(MENTION_REGEX)).toBe(null) - ); - }); - }); -}); diff --git a/webclient/src/types/constants.ts b/webclient/src/types/constants.ts deleted file mode 100644 index 7341a22b1..000000000 --- a/webclient/src/types/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -// eslint-disable-next-line -export const URL_REGEX = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b(?:[-a-zA-Z0-9@:%_\+.~#?&//=]*))/g; -export const MESSAGE_SENDER_REGEX = /(^[^:\s]+):/; -export const MENTION_REGEX = /(^|\s)(@\w+)/g; -export const CARD_CALLOUT_REGEX = /(\[\[[^\]]+\]\])/g; -export const CALLOUT_BOUNDARY_REGEX = /(\[\[|\]\])/g; diff --git a/webclient/src/types/countries.ts b/webclient/src/types/countries.ts deleted file mode 100644 index 0289b63ad..000000000 --- a/webclient/src/types/countries.ts +++ /dev/null @@ -1,253 +0,0 @@ -export const countryCodes = [ - 'AF', - 'AX', - 'AL', - 'DZ', - 'AS', - 'AD', - 'AO', - 'AI', - 'AQ', - 'AG', - 'AR', - 'AM', - 'AW', - 'AU', - 'AT', - 'AZ', - 'BH', - 'BS', - 'BD', - 'BB', - 'BY', - 'BE', - 'BZ', - 'BJ', - 'BM', - 'BT', - 'BO', - 'BQ', - 'BA', - 'BW', - 'BV', - 'BR', - 'IO', - 'BN', - 'BG', - 'BF', - 'BI', - 'KH', - 'CM', - 'CA', - 'CV', - 'KY', - 'CF', - 'TD', - 'CL', - 'CN', - 'CX', - 'CC', - 'CO', - 'KM', - 'CG', - 'CD', - 'CK', - 'CR', - 'CI', - 'HR', - 'CU', - 'CW', - 'CY', - 'CZ', - 'DK', - 'DJ', - 'DM', - 'DO', - 'EC', - 'EG', - 'SV', - 'GQ', - 'ER', - 'EE', - 'ET', - 'EU', - 'FK', - 'FO', - 'FJ', - 'FI', - 'FR', - 'GF', - 'PF', - 'TF', - 'GA', - 'GM', - 'GE', - 'DE', - 'GH', - 'GI', - 'GR', - 'GL', - 'GD', - 'GP', - 'GU', - 'GT', - 'GG', - 'GN', - 'GW', - 'GY', - 'HT', - 'HM', - 'VA', - 'HN', - 'HK', - 'HU', - 'IS', - 'IN', - 'ID', - 'IR', - 'IQ', - 'IE', - 'IM', - 'IL', - 'IT', - 'JM', - 'JP', - 'JE', - 'JO', - 'KZ', - 'KE', - 'KI', - 'KP', - 'KR', - 'KW', - 'KG', - 'LA', - 'LV', - 'LB', - 'LS', - 'LR', - 'LY', - 'LI', - 'LT', - 'LU', - 'MO', - 'MK', - 'MG', - 'MW', - 'MY', - 'MV', - 'ML', - 'MT', - 'MH', - 'MQ', - 'MR', - 'MU', - 'YT', - 'MX', - 'FM', - 'MD', - 'MC', - 'MN', - 'ME', - 'MS', - 'MA', - 'MZ', - 'MM', - 'NA', - 'NR', - 'NP', - 'NL', - 'NC', - 'NZ', - 'NI', - 'NE', - 'NG', - 'NU', - 'NF', - 'MP', - 'NO', - 'OM', - 'PK', - 'PW', - 'PS', - 'PA', - 'PG', - 'PY', - 'PE', - 'PH', - 'PN', - 'PL', - 'PT', - 'PR', - 'QA', - 'RE', - 'RO', - 'RU', - 'RW', - 'BL', - 'SH', - 'KN', - 'LC', - 'MF', - 'PM', - 'VC', - 'WS', - 'SM', - 'ST', - 'SA', - 'SN', - 'RS', - 'SC', - 'SL', - 'SG', - 'SX', - 'SK', - 'SI', - 'SB', - 'SO', - 'ZA', - 'GS', - 'SS', - 'ES', - 'LK', - 'SD', - 'SR', - 'SJ', - 'SZ', - 'SE', - 'CH', - 'SY', - 'TW', - 'TJ', - 'TZ', - 'TH', - 'TL', - 'TG', - 'TK', - 'TO', - 'TT', - 'TN', - 'TR', - 'TM', - 'TC', - 'TV', - 'UG', - 'UA', - 'AE', - 'GB', - 'US', - 'UM', - 'UY', - 'UZ', - 'VU', - 'VE', - 'VN', - 'VG', - 'VI', - 'WF', - 'EH', - 'YE', - 'XK', - 'ZM', - 'ZW', -]; diff --git a/webclient/src/types/deckList.ts b/webclient/src/types/deckList.ts deleted file mode 100644 index 9d7d792a6..000000000 --- a/webclient/src/types/deckList.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface DeckList { - root: DeckStorageFolder; -} - -export interface DeckStorageFolder { - items: DeckStorageTreeItem[]; -} - -export interface DeckStorageFile { - creationTime: number; -} - -export interface DeckStorageTreeItem { - id: number; - name: string; - file: DeckStorageFile | null; - folder: DeckStorageFolder | null; -} diff --git a/webclient/src/types/forms.ts b/webclient/src/types/forms.ts deleted file mode 100644 index 421bc2615..000000000 --- a/webclient/src/types/forms.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum FormKey { - ADD_TO_BUDDIES = 'ADD_TO_BUDDIES', - ADD_TO_IGNORE = 'ADD_TO_IGNORE', - CARD_IMPORT = 'CARD_IMPORT', - CONNECT = 'CONNECT', - LOGIN = 'LOGIN', - RESET_PASSWORD_REQUEST = 'RESET_PASSWORD_REQUEST', - RESET_PASSWORD = 'RESET_PASSWORD', - REGISTER = 'REGISTER', - SEARCH_LOGS = 'SEARCH_LOGS', -} diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts deleted file mode 100644 index b9fcc1dc2..000000000 --- a/webclient/src/types/game.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface Game { - description: string; - gameId: number; - gameType: string; - gameTypes: string[]; - roomId: number; - started: boolean; -} - -export enum GameSortField { - START_TIME = 'startTime' -} - -export interface GameConfig { - description: string; - password: string; - maxPlayer: number; - onlyBuddies: boolean; - onlyRegistered: boolean; - spectatorsAllowed: boolean; - spectatorsNeedPassword: boolean; - spectatorsCanTalk: boolean; - spectatorsSeeEverything: boolean; - gameTypeIds: number[]; - joinAsJudge: boolean; - joinAsSpectator: boolean; -} - -export interface JoinGameParams { - gameId: number; - password: string; - spectator: boolean; - overrideRestrictions: boolean; - joinAsJudge: boolean; -} - -export enum LeaveGameReason { - OTHER = 1, - USER_KICKED = 2, - USER_LEFT = 3, - USER_DISCONNECTED = 4 -} diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts deleted file mode 100644 index ded9962e5..000000000 --- a/webclient/src/types/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export * from './cards'; -export * from './constants'; -export * from './countries'; -export * from './game'; -export * from './room'; -export * from './server'; -export * from './sort'; -export * from './user'; -export * from './routes'; -export * from './sort'; -export * from './forms'; -export * from './message'; -export * from './settings'; -export * from './languages'; -export * from './logs'; -export * from './session'; -export * from './deckList'; -export * from './moderator'; -export * from './replay'; diff --git a/webclient/src/types/languages.ts b/webclient/src/types/languages.ts deleted file mode 100644 index 17699fd0e..000000000 --- a/webclient/src/types/languages.ts +++ /dev/null @@ -1,20 +0,0 @@ -export enum Language { - 'en-US' = 'en-US', - 'fr' = 'fr', - 'nl' = 'nl', - 'pt_BR' = 'pt_BR', -} - -export enum LanguageCountry { - 'en-US' = 'us', - 'fr' = 'fr', - 'nl' = 'nl', - 'pt_BR' = 'br' -} - -export enum LanguageNative { - 'en-US' = 'English - US', - 'fr' = 'Français', - 'nl' = 'Nederlands', - 'pt_BR' = 'Portugues do Brasil', -} diff --git a/webclient/src/types/logs.ts b/webclient/src/types/logs.ts deleted file mode 100644 index 3cf34b486..000000000 --- a/webclient/src/types/logs.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface LogFilters { - userName?: string; - ipAddress?: string; - gameName?: string; - gameId?: string; - message?: string; - logLocation?: string[]; - dateRange: number; - maximumResults?: number; -} diff --git a/webclient/src/types/message.ts b/webclient/src/types/message.ts deleted file mode 100644 index d7f256011..000000000 --- a/webclient/src/types/message.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface Message { - name: string; - message: string; - messageType: number; - timeOf: number; - timeReceived: number; -} diff --git a/webclient/src/types/moderator.ts b/webclient/src/types/moderator.ts deleted file mode 100644 index 5991ec6c1..000000000 --- a/webclient/src/types/moderator.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface BanHistoryItem { - adminId: string; - adminName: string; - banTime: string; - banLength: string; - banReason: string; - visibleReason: string; -} - -export interface WarnHistoryItem { - userName: string; - adminName: string; - reason: string; - timeOf: string; -} - -export interface WarnListItem { - warning: string; - userName: string; - userClientid: string; -} diff --git a/webclient/src/types/replay.ts b/webclient/src/types/replay.ts deleted file mode 100644 index dfa78538a..000000000 --- a/webclient/src/types/replay.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface Replay { - replayId: number; - replayName: string; - duration: number; -} - -export interface ReplayMatch { - replayList: Replay[]; - gameId: number; - roomName: string; - timeStarted: number; - length: number; - gameName: string; - playerNames: string[]; - doNotHide: boolean; -} diff --git a/webclient/src/types/room.ts b/webclient/src/types/room.ts deleted file mode 100644 index e69b1a499..000000000 --- a/webclient/src/types/room.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { User } from './user'; - -export interface Room { - autoJoin: boolean - description: string; - gameCount: number; - gameList: any[]; - gametypeList: any[]; - gametypeMap: GametypeMap; - name: string; - permissionlevel: RoomAccessLevel; - playerCount: number; - privilegelevel: RoomAccessLevel; - roomId: number; - userList: User[]; - order: number; -} - -export interface GametypeMap { [index: number]: string } - -export enum RoomAccessLevel { - 'none' -} diff --git a/webclient/src/types/routes.ts b/webclient/src/types/routes.ts deleted file mode 100644 index d91e8a7cb..000000000 --- a/webclient/src/types/routes.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum RouteEnum { - PLAYER = '/player/:name', - SERVER = '/server', - ROOM = '/room/:roomId', - LOGIN = '/login', - LOGS = '/logs', - GAME = '/game', - DECKS = '/decks', - DECK = '/deck', - ACCOUNT = '/account', - ADMINISTRATION = '/administration', - REPLAYS = '/replays', - INITIALIZE = '/initialize', - UNSUPPORTED = '/unsupported', -} diff --git a/webclient/src/types/server.ts b/webclient/src/types/server.ts deleted file mode 100644 index 305a5a810..000000000 --- a/webclient/src/types/server.ts +++ /dev/null @@ -1,127 +0,0 @@ -export interface ServerStatus { - status: StatusEnum; - description: string; -} - -export enum StatusEnum { - DISCONNECTED, - CONNECTING, - CONNECTED, - LOGGING_IN, - LOGGED_IN, - DISCONNECTING = 99 -} - -export interface WebSocketConnectOptions { - host?: string; - port?: string; - userName?: string; - password?: string; - hashedPassword?: string; - newPassword?: string; - token?: string; - email?: string; - realName?: string; - country?: string; - autojoinrooms?: boolean; - keepalive?: number; - clientid?: string; - reason?: WebSocketConnectReason; -} - -export enum WebSocketConnectReason { - LOGIN, - REGISTER, - ACTIVATE_ACCOUNT, - PASSWORD_RESET_REQUEST, - PASSWORD_RESET_CHALLENGE, - PASSWORD_RESET, - TEST_CONNECTION, -} - -export class Host { - id?: number; - name: string; - host: string; - port: string; - localHost?: string; - localPort?: string; - editable: boolean; - lastSelected?: boolean; - userName?: string; - hashedPassword?: string; - remember?: boolean; -} - -export const DefaultHosts: Host[] = [ - { - name: 'Chickatrice', - host: 'mtg.chickatrice.net', - port: '443', - localPort: '4748', - editable: false, - }, - { - name: 'Rooster', - host: 'server.cockatrice.us/servatrice', - port: '4748', - localHost: 'server.cockatrice.us', - editable: false, - }, - { - name: 'Rooster Beta', - host: 'beta.cockatrice.us/servatrice', - port: '4748', - localHost: 'beta.cockatrice.us', - editable: false, - }, - { - name: 'Tetrarch', - host: 'mtg.tetrarch.co/servatrice', - port: '443', - editable: false, - }, -]; - -export const getHostPort = (host: Host): { host: string, port: string } => { - const isLocal = window.location.hostname === 'localhost'; - - if (!host) { - return { - host: '', - port: '' - }; - } - - return { - host: !isLocal ? host.host : host.localHost || host.host, - port: !isLocal ? host.port : host.localPort || host.port, - } -}; - -export enum KnownHost { - ROOSTER = 'Rooster', - TETRARCH = 'Tetrarch', -} - -export const KnownHosts = { - [KnownHost.ROOSTER]: { port: 4748, host: 'server.cockatrice.us', }, - [KnownHost.TETRARCH]: { port: 443, host: 'mtg.tetrarch.co/servatrice' }, -} - -export interface LogItem { - message: string; - senderId: string; - senderIp: string; - senderName: string; - targetId: string; - targetName: string; - targetType: string; - time: string; -} - -export interface LogGroups { - room: LogItem[]; - game: LogItem[]; - chat: LogItem[]; -} diff --git a/webclient/src/types/session.ts b/webclient/src/types/session.ts deleted file mode 100644 index 1b49616c5..000000000 --- a/webclient/src/types/session.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum NotificationType { - UNKNOWN = 0, - PROMOTED = 1, - WARNING = 2, - IDLEWARNING = 3, - CUSTOM = 4, -}; diff --git a/webclient/src/types/settings.ts b/webclient/src/types/settings.ts deleted file mode 100644 index c91394331..000000000 --- a/webclient/src/types/settings.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class Setting { - user: string; - autoConnect?: boolean; -} - -export const APP_USER = '*app'; diff --git a/webclient/src/types/sort.ts b/webclient/src/types/sort.ts deleted file mode 100644 index c3312732c..000000000 --- a/webclient/src/types/sort.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum SortDirection { - ASC = 'ASC', - DESC = 'DESC' -} - -export interface SortBy { - field: string; - order: SortDirection; -} diff --git a/webclient/src/types/user.ts b/webclient/src/types/user.ts deleted file mode 100644 index 2d0383eb7..000000000 --- a/webclient/src/types/user.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface User { - accountageSecs: number; - name: string; - privlevel: UserPrivLevel; - userLevel: number; - realName?: string; - country?: string; - avatarBmp?: Uint8Array; -} - -export enum UserLevelFlag { - IsNothing = 0, - IsUser = 1, - IsRegistered = 2, - IsModerator = 4, - IsAdmin = 8, - IsJudge = 16, -} - -export enum UserPrivLevel { - NONE = 0, - VIP = 1, - DONOR = 2 -} - -export enum UserSortField { - NAME = 'name' -} diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts deleted file mode 100644 index 1dae24d0c..000000000 --- a/webclient/src/websocket/WebClient.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import { ProtobufService } from './services/ProtobufService'; -import { WebSocketService } from './services/WebSocketService'; - -import { RoomPersistence, SessionPersistence } from './persistence'; - -export class WebClient { - public socket = new WebSocketService(this); - public protobuf = new ProtobufService(this); - - public protocolVersion = 14; - public clientConfig = { - clientid: 'webatrice', - clientver: 'webclient-1.0 (2019-10-31)', - clientfeatures: [ - 'client_id', - 'client_ver', - 'feature_set', - 'room_chat_history', - 'client_warnings', - /* unimplemented features */ - 'forgot_password', - 'idle_client', - 'mod_log_lookup', - 'user_ban_history', - // satisfy server reqs for POC - 'websocket', - '2.7.0_min_version', - '2.8.0_min_version' - ] - }; - - public clientOptions = { - autojoinrooms: true, - keepalive: 5000 - }; - - public options: WebSocketConnectOptions; - public status: StatusEnum; - - public connectionAttemptMade = false; - - constructor() { - this.socket.message$.subscribe((message: MessageEvent) => { - this.protobuf.handleMessageEvent(message); - }); - - if (process.env.NODE_ENV !== 'test') { - console.log(this); - } - } - - public connect(options: WebSocketConnectOptions) { - this.connectionAttemptMade = true; - this.options = options; - this.socket.connect(options); - } - - public testConnect(options: WebSocketConnectOptions) { - this.socket.testConnect(options); - } - - public disconnect() { - this.socket.disconnect(); - } - - public updateStatus(status: StatusEnum) { - this.status = status; - - if (status === StatusEnum.DISCONNECTED) { - this.protobuf.resetCommands(); - this.clearStores(); - } - } - - public keepAlive(pingReceived: Function) { - this.protobuf.sendKeepAliveCommand(pingReceived); - } - - private clearStores() { - RoomPersistence.clearStore(); - SessionPersistence.clearStore(); - } -} - -const webClient = new WebClient(); - -export default webClient; diff --git a/webclient/src/websocket/commands/admin/adjustMod.ts b/webclient/src/websocket/commands/admin/adjustMod.ts deleted file mode 100644 index fd71fece1..000000000 --- a/webclient/src/websocket/commands/admin/adjustMod.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { AdminPersistence } from '../../persistence'; - -export function adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - BackendService.sendAdminCommand('Command_AdjustMod', { userName, shouldBeMod, shouldBeJudge }, { - onSuccess: () => { - AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); - }, - }); -} diff --git a/webclient/src/websocket/commands/admin/index.ts b/webclient/src/websocket/commands/admin/index.ts deleted file mode 100644 index 5d4ae21bf..000000000 --- a/webclient/src/websocket/commands/admin/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './adjustMod'; -export * from './reloadConfig'; -export * from './shutdownServer'; -export * from './updateServerMessage'; diff --git a/webclient/src/websocket/commands/admin/reloadConfig.ts b/webclient/src/websocket/commands/admin/reloadConfig.ts deleted file mode 100644 index 979f3ec73..000000000 --- a/webclient/src/websocket/commands/admin/reloadConfig.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { AdminPersistence } from '../../persistence'; - -export function reloadConfig(): void { - BackendService.sendAdminCommand('Command_ReloadConfig', {}, { - onSuccess: () => { - AdminPersistence.reloadConfig(); - }, - }); -} diff --git a/webclient/src/websocket/commands/admin/shutdownServer.ts b/webclient/src/websocket/commands/admin/shutdownServer.ts deleted file mode 100644 index e65c900db..000000000 --- a/webclient/src/websocket/commands/admin/shutdownServer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { AdminPersistence } from '../../persistence'; - -export function shutdownServer(reason: string, minutes: number): void { - BackendService.sendAdminCommand('Command_ShutdownServer', { reason, minutes }, { - onSuccess: () => { - AdminPersistence.shutdownServer(); - }, - }); -} diff --git a/webclient/src/websocket/commands/admin/updateServerMessage.ts b/webclient/src/websocket/commands/admin/updateServerMessage.ts deleted file mode 100644 index e2b194514..000000000 --- a/webclient/src/websocket/commands/admin/updateServerMessage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { AdminPersistence } from '../../persistence'; - -export function updateServerMessage(): void { - BackendService.sendAdminCommand('Command_UpdateServerMessage', {}, { - onSuccess: () => { - AdminPersistence.updateServerMessage(); - }, - }); -} diff --git a/webclient/src/websocket/commands/index.ts b/webclient/src/websocket/commands/index.ts deleted file mode 100644 index 2c68fcfb9..000000000 --- a/webclient/src/websocket/commands/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * as AdminCommands from './admin'; -export * as ModeratorCommands from './moderator'; -export * as RoomCommands from './room'; -export * as SessionCommands from './session'; diff --git a/webclient/src/websocket/commands/moderator/banFromServer.ts b/webclient/src/websocket/commands/moderator/banFromServer.ts deleted file mode 100644 index e45e34504..000000000 --- a/webclient/src/websocket/commands/moderator/banFromServer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function banFromServer(minutes: number, userName?: string, address?: string, reason?: string, - visibleReason?: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand('Command_BanFromServer', { - minutes, userName, address, reason, visibleReason, clientid, removeMessages - }, { - onSuccess: () => { - ModeratorPersistence.banFromServer(userName); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/forceActivateUser.ts b/webclient/src/websocket/commands/moderator/forceActivateUser.ts deleted file mode 100644 index d4138a015..000000000 --- a/webclient/src/websocket/commands/moderator/forceActivateUser.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function forceActivateUser(usernameToActivate: string, moderatorName: string): void { - BackendService.sendModeratorCommand('Command_ForceActivateUser', { usernameToActivate, moderatorName }, { - onSuccess: () => { - ModeratorPersistence.forceActivateUser(usernameToActivate, moderatorName); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/getAdminNotes.ts b/webclient/src/websocket/commands/moderator/getAdminNotes.ts deleted file mode 100644 index d4f626aa2..000000000 --- a/webclient/src/websocket/commands/moderator/getAdminNotes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function getAdminNotes(userName: string): void { - BackendService.sendModeratorCommand('Command_GetAdminNotes', { userName }, { - responseName: 'Response_GetAdminNotes', - onSuccess: (response) => { - ModeratorPersistence.getAdminNotes(userName, response.notes); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/getBanHistory.ts b/webclient/src/websocket/commands/moderator/getBanHistory.ts deleted file mode 100644 index dd4e90eda..000000000 --- a/webclient/src/websocket/commands/moderator/getBanHistory.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function getBanHistory(userName: string): void { - BackendService.sendModeratorCommand('Command_GetBanHistory', { userName }, { - responseName: 'Response_BanHistory', - onSuccess: (response) => { - ModeratorPersistence.banHistory(userName, response.banList); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/getWarnHistory.ts b/webclient/src/websocket/commands/moderator/getWarnHistory.ts deleted file mode 100644 index c47e2c6e4..000000000 --- a/webclient/src/websocket/commands/moderator/getWarnHistory.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function getWarnHistory(userName: string): void { - BackendService.sendModeratorCommand('Command_GetWarnHistory', { userName }, { - responseName: 'Response_WarnHistory', - onSuccess: (response) => { - ModeratorPersistence.warnHistory(userName, response.warnList); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts deleted file mode 100644 index 412aee09e..000000000 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function getWarnList(modName: string, userName: string, userClientid: string): void { - BackendService.sendModeratorCommand('Command_GetWarnList', { modName, userName, userClientid }, { - responseName: 'Response_WarnList', - onSuccess: (response) => { - ModeratorPersistence.warnListOptions(response.warning); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts deleted file mode 100644 index 74d64d17a..000000000 --- a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function grantReplayAccess(replayId: number, moderatorName: string): void { - BackendService.sendModeratorCommand('Command_GrantReplayAccess', { replayId, moderatorName }, { - onSuccess: () => { - ModeratorPersistence.grantReplayAccess(replayId, moderatorName); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/index.ts b/webclient/src/websocket/commands/moderator/index.ts deleted file mode 100644 index 10bb0e1c6..000000000 --- a/webclient/src/websocket/commands/moderator/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './banFromServer'; -export * from './forceActivateUser'; -export * from './getAdminNotes'; -export * from './getBanHistory'; -export * from './getWarnHistory'; -export * from './getWarnList'; -export * from './grantReplayAccess'; -export * from './updateAdminNotes'; -export * from './viewLogHistory'; -export * from './warnUser'; diff --git a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts deleted file mode 100644 index c7ac315c5..000000000 --- a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function updateAdminNotes(userName: string, notes: string): void { - BackendService.sendModeratorCommand('Command_UpdateAdminNotes', { userName, notes }, { - onSuccess: () => { - ModeratorPersistence.updateAdminNotes(userName, notes); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/viewLogHistory.ts b/webclient/src/websocket/commands/moderator/viewLogHistory.ts deleted file mode 100644 index 19a930608..000000000 --- a/webclient/src/websocket/commands/moderator/viewLogHistory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; -import { LogFilters } from 'types'; - -export function viewLogHistory(filters: LogFilters): void { - BackendService.sendModeratorCommand('Command_ViewLogHistory', filters, { - responseName: 'Response_ViewLogHistory', - onSuccess: (response) => { - ModeratorPersistence.viewLogs(response.logMessage); - }, - }); -} diff --git a/webclient/src/websocket/commands/moderator/warnUser.ts b/webclient/src/websocket/commands/moderator/warnUser.ts deleted file mode 100644 index 0e0271d4b..000000000 --- a/webclient/src/websocket/commands/moderator/warnUser.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { ModeratorPersistence } from '../../persistence'; - -export function warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand('Command_WarnUser', { userName, reason, clientid, removeMessages }, { - onSuccess: () => { - ModeratorPersistence.warnUser(userName); - }, - }); -} diff --git a/webclient/src/websocket/commands/room/createGame.ts b/webclient/src/websocket/commands/room/createGame.ts deleted file mode 100644 index 62565e0e6..000000000 --- a/webclient/src/websocket/commands/room/createGame.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { RoomPersistence } from '../../persistence'; -import { GameConfig } from 'types'; - -export function createGame(roomId: number, gameConfig: GameConfig): void { - BackendService.sendRoomCommand(roomId, 'Command_CreateGame', gameConfig, { - onSuccess: () => { - RoomPersistence.gameCreated(roomId); - }, - }); -} diff --git a/webclient/src/websocket/commands/room/index.ts b/webclient/src/websocket/commands/room/index.ts deleted file mode 100644 index 18235618c..000000000 --- a/webclient/src/websocket/commands/room/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './createGame'; -export * from './joinGame'; -export * from './leaveRoom'; -export * from './roomSay'; diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts deleted file mode 100644 index ef4b1fff2..000000000 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { RoomPersistence } from '../../persistence'; -import { JoinGameParams } from 'types'; - -export function joinGame(roomId: number, joinGameParams: JoinGameParams): void { - BackendService.sendRoomCommand(roomId, 'Command_JoinGame', joinGameParams, { - onSuccess: () => { - RoomPersistence.joinedGame(roomId, joinGameParams.gameId); - }, - }); -} diff --git a/webclient/src/websocket/commands/room/leaveRoom.ts b/webclient/src/websocket/commands/room/leaveRoom.ts deleted file mode 100644 index 7cd64a0e2..000000000 --- a/webclient/src/websocket/commands/room/leaveRoom.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { RoomPersistence } from '../../persistence'; - -export function leaveRoom(roomId: number): void { - BackendService.sendRoomCommand(roomId, 'Command_LeaveRoom', {}, { - onSuccess: () => { - RoomPersistence.leaveRoom(roomId); - }, - }); -} diff --git a/webclient/src/websocket/commands/room/roomSay.ts b/webclient/src/websocket/commands/room/roomSay.ts deleted file mode 100644 index a429845be..000000000 --- a/webclient/src/websocket/commands/room/roomSay.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; - -export function roomSay(roomId: number, message: string): void { - const trimmed = message.trim(); - - if (!trimmed) { - return; - } - - BackendService.sendRoomCommand(roomId, 'Command_RoomSay', { message: trimmed }, {}); -} diff --git a/webclient/src/websocket/commands/session/accountEdit.ts b/webclient/src/websocket/commands/session/accountEdit.ts deleted file mode 100644 index 31bf2d3f6..000000000 --- a/webclient/src/websocket/commands/session/accountEdit.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function accountEdit(passwordCheck: string, realName?: string, email?: string, country?: string): void { - BackendService.sendSessionCommand('Command_AccountEdit', { passwordCheck, realName, email, country }, { - onSuccess: () => { - SessionPersistence.accountEditChanged(realName, email, country); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/accountImage.ts b/webclient/src/websocket/commands/session/accountImage.ts deleted file mode 100644 index cd0e24403..000000000 --- a/webclient/src/websocket/commands/session/accountImage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function accountImage(image: Uint8Array): void { - BackendService.sendSessionCommand('Command_AccountImage', { image }, { - onSuccess: () => { - SessionPersistence.accountImageChanged(image); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/accountPassword.ts b/webclient/src/websocket/commands/session/accountPassword.ts deleted file mode 100644 index 81c7a993b..000000000 --- a/webclient/src/websocket/commands/session/accountPassword.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function accountPassword(oldPassword: string, newPassword: string, hashedNewPassword: string): void { - BackendService.sendSessionCommand('Command_AccountPassword', { oldPassword, newPassword, hashedNewPassword }, { - onSuccess: () => { - SessionPersistence.accountPasswordChange(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts deleted file mode 100644 index 4cd0e8c4e..000000000 --- a/webclient/src/websocket/commands/session/activate.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AccountActivationParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; -import { SessionPersistence } from '../../persistence'; - -import { disconnect, login, updateStatus } from './'; - -export function activate(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, token } = options as unknown as AccountActivationParams; - - BackendService.sendSessionCommand('Command_Activate', { - ...webClient.clientConfig, - userName, - token, - }, { - onResponseCode: { - [ProtoController.root.Response.ResponseCode.RespActivationAccepted]: () => { - SessionPersistence.accountActivationSuccess(); - login(options, passwordSalt); - }, - }, - onError: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed'); - disconnect(); - SessionPersistence.accountActivationFailed(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/addToList.ts b/webclient/src/websocket/commands/session/addToList.ts deleted file mode 100644 index c5bc3c5f0..000000000 --- a/webclient/src/websocket/commands/session/addToList.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function addToBuddyList(userName: string): void { - addToList('buddy', userName); -} - -export function addToIgnoreList(userName: string): void { - addToList('ignore', userName); -} - -export function addToList(list: string, userName: string): void { - BackendService.sendSessionCommand('Command_AddToList', { list, userName }, { - onSuccess: () => { - SessionPersistence.addToList(list, userName); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/connect.ts b/webclient/src/websocket/commands/session/connect.ts deleted file mode 100644 index 5b660fab1..000000000 --- a/webclient/src/websocket/commands/session/connect.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; -import webClient from '../../WebClient'; -import { updateStatus } from './'; - -export function connect(options: WebSocketConnectOptions, reason: WebSocketConnectReason): void { - switch (reason) { - case WebSocketConnectReason.LOGIN: - case WebSocketConnectReason.REGISTER: - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - case WebSocketConnectReason.PASSWORD_RESET_REQUEST: - case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: - case WebSocketConnectReason.PASSWORD_RESET: - updateStatus(StatusEnum.CONNECTING, 'Connecting...'); - break; - case WebSocketConnectReason.TEST_CONNECTION: - webClient.testConnect({ ...options }); - return; - default: - updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason); - return; - } - - webClient.connect({ ...options, reason }); -} diff --git a/webclient/src/websocket/commands/session/deckDel.ts b/webclient/src/websocket/commands/session/deckDel.ts deleted file mode 100644 index 752ce78d5..000000000 --- a/webclient/src/websocket/commands/session/deckDel.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function deckDel(deckId: number): void { - BackendService.sendSessionCommand('Command_DeckDel', { deckId }, { - onSuccess: () => { - SessionPersistence.deleteServerDeck(deckId); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/deckDelDir.ts b/webclient/src/websocket/commands/session/deckDelDir.ts deleted file mode 100644 index df5bbc223..000000000 --- a/webclient/src/websocket/commands/session/deckDelDir.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function deckDelDir(path: string): void { - BackendService.sendSessionCommand('Command_DeckDelDir', { path }, { - onSuccess: () => { - SessionPersistence.deleteServerDeckDir(path); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts deleted file mode 100644 index 3d5a3499a..000000000 --- a/webclient/src/websocket/commands/session/deckList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function deckList(): void { - BackendService.sendSessionCommand('Command_DeckList', {}, { - responseName: 'Response_DeckList', - onSuccess: (response) => { - SessionPersistence.updateServerDecks(response); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/deckNewDir.ts b/webclient/src/websocket/commands/session/deckNewDir.ts deleted file mode 100644 index 85ab16afb..000000000 --- a/webclient/src/websocket/commands/session/deckNewDir.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function deckNewDir(path: string, dirName: string): void { - BackendService.sendSessionCommand('Command_DeckNewDir', { path, dirName }, { - onSuccess: () => { - SessionPersistence.createServerDeckDir(path, dirName); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts deleted file mode 100644 index 2679c4e8e..000000000 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function deckUpload(path: string, deckId: number, deckList: string): void { - BackendService.sendSessionCommand('Command_DeckUpload', { path, deckId, deckList }, { - responseName: 'Response_DeckUpload', - onSuccess: (response) => { - SessionPersistence.uploadServerDeck(path, response.newFile); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/disconnect.ts b/webclient/src/websocket/commands/session/disconnect.ts deleted file mode 100644 index 9fe567677..000000000 --- a/webclient/src/websocket/commands/session/disconnect.ts +++ /dev/null @@ -1,5 +0,0 @@ -import webClient from '../../WebClient'; - -export function disconnect(): void { - webClient.disconnect(); -} diff --git a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts deleted file mode 100644 index 05af1ccf9..000000000 --- a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ForgotPasswordChallengeParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; -import { disconnect, updateStatus } from './'; - -export function forgotPasswordChallenge(options: WebSocketConnectOptions): void { - const { userName, email } = options as unknown as ForgotPasswordChallengeParams; - - BackendService.sendSessionCommand('Command_ForgotPasswordChallenge', { - ...webClient.clientConfig, - userName, - email, - }, { - onSuccess: () => { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPassword(); - disconnect(); - }, - onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPasswordFailed(); - disconnect(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts deleted file mode 100644 index 23d301450..000000000 --- a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ForgotPasswordParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -import { disconnect, updateStatus } from './'; - -export function forgotPasswordRequest(options: WebSocketConnectOptions): void { - const { userName } = options as unknown as ForgotPasswordParams; - - BackendService.sendSessionCommand('Command_ForgotPasswordRequest', { - ...webClient.clientConfig, - userName, - }, { - responseName: 'Response_ForgotPasswordRequest', - onSuccess: (resp) => { - if (resp?.challengeEmail) { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPasswordChallenge(); - } else { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPassword(); - } - disconnect(); - }, - onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPasswordFailed(); - disconnect(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts deleted file mode 100644 index d9a775816..000000000 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ForgotPasswordResetParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; -import { hashPassword } from '../../utils'; - -import { disconnect, updateStatus } from '.'; - -export function forgotPasswordReset(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, token, newPassword } = options as unknown as ForgotPasswordResetParams; - - const params: any = { - ...webClient.clientConfig, - userName, - token, - }; - - if (passwordSalt) { - params.hashedNewPassword = hashPassword(passwordSalt, newPassword); - } else { - params.newPassword = newPassword; - } - - BackendService.sendSessionCommand('Command_ForgotPasswordReset', params, { - onSuccess: () => { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPasswordSuccess(); - disconnect(); - }, - onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); - SessionPersistence.resetPasswordFailed(); - disconnect(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/getGamesOfUser.ts b/webclient/src/websocket/commands/session/getGamesOfUser.ts deleted file mode 100644 index 8fb8aeb5b..000000000 --- a/webclient/src/websocket/commands/session/getGamesOfUser.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function getGamesOfUser(userName: string): void { - BackendService.sendSessionCommand('Command_GetGamesOfUser', { userName }, { - responseName: 'Response_GetGamesOfUser', - onSuccess: (response) => { - SessionPersistence.getGamesOfUser(userName, response); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/getUserInfo.ts b/webclient/src/websocket/commands/session/getUserInfo.ts deleted file mode 100644 index 5b0f178ae..000000000 --- a/webclient/src/websocket/commands/session/getUserInfo.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function getUserInfo(userName: string): void { - BackendService.sendSessionCommand('Command_GetUserInfo', { userName }, { - responseName: 'Response_GetUserInfo', - onSuccess: (response) => { - SessionPersistence.getUserInfo(response.userInfo); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/index.ts b/webclient/src/websocket/commands/session/index.ts deleted file mode 100644 index 74d0d062c..000000000 --- a/webclient/src/websocket/commands/session/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from './accountEdit'; -export * from './accountImage'; -export * from './accountPassword'; -export * from './activate'; -export * from './addToList'; -export * from './connect'; -export * from './deckDel'; -export * from './deckDelDir'; -export * from './deckList'; -export * from './deckNewDir'; -export * from './deckUpload'; -export * from './disconnect'; -export * from './forgotPasswordChallenge'; -export * from './forgotPasswordRequest'; -export * from './forgotPasswordReset'; -export * from './getGamesOfUser'; -export * from './getUserInfo'; -export * from './joinRoom'; -export * from './listRooms'; -export * from './listUsers'; -export * from './login'; -export * from './message'; -export * from './ping'; -export * from './register'; -export * from './removeFromList'; -export * from './replayDeleteMatch'; -export * from './replayList'; -export * from './replayModifyMatch'; -export * from './requestPasswordSalt'; -export * from './updateStatus'; diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts deleted file mode 100644 index be79976a0..000000000 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { RoomPersistence } from '../../persistence'; - -export function joinRoom(roomId: number): void { - BackendService.sendSessionCommand('Command_JoinRoom', { roomId }, { - responseName: 'Response_JoinRoom', - onSuccess: (response) => { - RoomPersistence.joinRoom(response.roomInfo); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/listRooms.ts b/webclient/src/websocket/commands/session/listRooms.ts deleted file mode 100644 index 367dada9b..000000000 --- a/webclient/src/websocket/commands/session/listRooms.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BackendService } from '../../services/BackendService'; - -export function listRooms(): void { - BackendService.sendSessionCommand('Command_ListRooms', {}, {}); -} diff --git a/webclient/src/websocket/commands/session/listUsers.ts b/webclient/src/websocket/commands/session/listUsers.ts deleted file mode 100644 index 9b95c1344..000000000 --- a/webclient/src/websocket/commands/session/listUsers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function listUsers(): void { - BackendService.sendSessionCommand('Command_ListUsers', {}, { - responseName: 'Response_ListUsers', - onSuccess: (response) => { - SessionPersistence.updateUsers(response.userList); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts deleted file mode 100644 index 6f3ec5ef5..000000000 --- a/webclient/src/websocket/commands/session/login.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { StatusEnum, WebSocketConnectOptions } from 'types'; -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; -import { hashPassword } from '../../utils'; -import { SessionPersistence } from '../../persistence'; - -import { - disconnect, - listUsers, - listRooms, - updateStatus, -} from './'; - -export function login(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, password, hashedPassword } = options; - - const loginConfig: any = { - ...webClient.clientConfig, - clientid: 'webatrice', - userName, - }; - - if (passwordSalt) { - loginConfig.hashedPassword = hashedPassword || hashPassword(passwordSalt, password); - } else { - loginConfig.password = password; - } - - const { ResponseCode } = ProtoController.root.Response; - - const onLoginError = (message: string, extra?: () => void) => { - updateStatus(StatusEnum.DISCONNECTED, message); - extra?.(); - SessionPersistence.loginFailed(); - disconnect(); - }; - - BackendService.sendSessionCommand('Command_Login', loginConfig, { - responseName: 'Response_Login', - onSuccess: (resp) => { - const { buddyList, ignoreList, userInfo } = resp; - - SessionPersistence.updateBuddyList(buddyList); - SessionPersistence.updateIgnoreList(ignoreList); - SessionPersistence.updateUser(userInfo); - SessionPersistence.loginSuccessful(loginConfig); - - listUsers(); - listRooms(); - - updateStatus(StatusEnum.LOGGED_IN, 'Logged in.'); - }, - onResponseCode: { - [ResponseCode.RespClientUpdateRequired]: () => - onLoginError('Login failed: missing features'), - [ResponseCode.RespWrongPassword]: () => - onLoginError('Login failed: incorrect username or password'), - [ResponseCode.RespUsernameInvalid]: () => - onLoginError('Login failed: incorrect username or password'), - [ResponseCode.RespWouldOverwriteOldSession]: () => - onLoginError('Login failed: duplicated user session'), - [ResponseCode.RespUserIsBanned]: () => - onLoginError('Login failed: banned user'), - [ResponseCode.RespRegistrationRequired]: () => - onLoginError('Login failed: registration required'), - [ResponseCode.RespClientIdRequired]: () => - onLoginError('Login failed: missing client ID'), - [ResponseCode.RespContextError]: () => - onLoginError('Login failed: server error'), - [ResponseCode.RespAccountNotActivated]: () => - onLoginError('Login failed: account not activated', - () => SessionPersistence.accountAwaitingActivation(options) - ), - }, - onError: (responseCode) => - onLoginError(`Login failed: unknown error: ${responseCode}`), - }); -} diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts deleted file mode 100644 index 075fc3c4b..000000000 --- a/webclient/src/websocket/commands/session/message.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function message(userName: string, message: string): void { - BackendService.sendSessionCommand('Command_Message', { userName, message }, { - onSuccess: () => { - SessionPersistence.directMessageSent(userName, message); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts deleted file mode 100644 index fea2784a2..000000000 --- a/webclient/src/websocket/commands/session/ping.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BackendService } from '../../services/BackendService'; - -export function ping(pingReceived: Function): void { - BackendService.sendSessionCommand('Command_Ping', {}, { - onResponse: (raw) => pingReceived(raw), - }); -} diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts deleted file mode 100644 index a25b85868..000000000 --- a/webclient/src/websocket/commands/session/register.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ServerRegisterParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; -import { SessionPersistence } from '../../persistence'; -import { hashPassword } from '../../utils'; - -import { login, disconnect, updateStatus } from './'; - -export function register(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, password, email, country, realName } = options as ServerRegisterParams; - - const params: any = { - ...webClient.clientConfig, - userName, - email, - country, - realName, - }; - - if (passwordSalt) { - params.hashedPassword = hashPassword(passwordSalt, password); - } else { - params.password = password; - } - - const { ResponseCode } = ProtoController.root.Response; - - const onRegistrationError = (action: () => void) => { - action(); - updateStatus(StatusEnum.DISCONNECTED, 'Registration failed'); - disconnect(); - }; - - BackendService.sendSessionCommand('Command_Register', params, { - onResponseCode: { - [ResponseCode.RespRegistrationAccepted]: () => { - login(options, passwordSalt); - SessionPersistence.registrationSuccess(); - }, - [ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Registration accepted, awaiting activation'); - SessionPersistence.accountAwaitingActivation(options); - disconnect(); - }, - [ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( - () => SessionPersistence.registrationUserNameError('Username is taken') - ), - [ResponseCode.RespUsernameInvalid]: () => onRegistrationError( - () => SessionPersistence.registrationUserNameError('Invalid username') - ), - [ResponseCode.RespPasswordTooShort]: () => onRegistrationError( - () => SessionPersistence.registrationPasswordError('Your password was too short') - ), - [ResponseCode.RespEmailRequiredToRegister]: () => onRegistrationError( - () => SessionPersistence.registrationRequiresEmail() - ), - [ResponseCode.RespEmailBlackListed]: () => onRegistrationError( - () => SessionPersistence.registrationEmailError('This email provider has been blocked') - ), - [ResponseCode.RespTooManyRequests]: () => onRegistrationError( - () => SessionPersistence.registrationEmailError('Max accounts reached for this email') - ), - [ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( - () => SessionPersistence.registrationFailed('Registration is currently disabled') - ), - [ResponseCode.RespUserIsBanned]: (raw) => onRegistrationError( - () => SessionPersistence.registrationFailed(raw.reasonStr, raw.endTime) - ), - }, - onError: () => onRegistrationError( - () => SessionPersistence.registrationFailed('Registration failed due to a server issue') - ), - }); -} diff --git a/webclient/src/websocket/commands/session/removeFromList.ts b/webclient/src/websocket/commands/session/removeFromList.ts deleted file mode 100644 index aede49c49..000000000 --- a/webclient/src/websocket/commands/session/removeFromList.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function removeFromBuddyList(userName: string): void { - removeFromList('buddy', userName); -} - -export function removeFromIgnoreList(userName: string): void { - removeFromList('ignore', userName); -} - -export function removeFromList(list: string, userName: string): void { - BackendService.sendSessionCommand('Command_RemoveFromList', { list, userName }, { - onSuccess: () => { - SessionPersistence.removeFromList(list, userName); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/replayDeleteMatch.ts b/webclient/src/websocket/commands/session/replayDeleteMatch.ts deleted file mode 100644 index 24ac48f1c..000000000 --- a/webclient/src/websocket/commands/session/replayDeleteMatch.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function replayDeleteMatch(gameId: number): void { - BackendService.sendSessionCommand('Command_ReplayDeleteMatch', { gameId }, { - onSuccess: () => { - SessionPersistence.replayDeleteMatch(gameId); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/replayList.ts b/webclient/src/websocket/commands/session/replayList.ts deleted file mode 100644 index f39eb279f..000000000 --- a/webclient/src/websocket/commands/session/replayList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function replayList(): void { - BackendService.sendSessionCommand('Command_ReplayList', {}, { - responseName: 'Response_ReplayList', - onSuccess: (response) => { - SessionPersistence.replayList(response.matchList); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/replayModifyMatch.ts b/webclient/src/websocket/commands/session/replayModifyMatch.ts deleted file mode 100644 index 9825047f3..000000000 --- a/webclient/src/websocket/commands/session/replayModifyMatch.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; - -export function replayModifyMatch(gameId: number, doNotHide: boolean): void { - BackendService.sendSessionCommand('Command_ReplayModifyMatch', { gameId, doNotHide }, { - onSuccess: () => { - SessionPersistence.replayModifyMatch(gameId, doNotHide); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts deleted file mode 100644 index a3d1fc05c..000000000 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { RequestPasswordSaltParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; - -import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; -import { SessionPersistence } from '../../persistence'; - -import { - activate, - disconnect, - login, - forgotPasswordReset, - updateStatus -} from './'; - -export function requestPasswordSalt(options: WebSocketConnectOptions): void { - const { userName } = options as RequestPasswordSaltParams; - - const onFailure = () => { - switch (options.reason) { - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - SessionPersistence.accountActivationFailed(); - break; - case WebSocketConnectReason.PASSWORD_RESET: - SessionPersistence.resetPasswordFailed(); - break; - default: - SessionPersistence.loginFailed(); - } - disconnect(); - }; - - BackendService.sendSessionCommand('Command_RequestPasswordSalt', { - ...webClient.clientConfig, - userName, - }, { - responseName: 'Response_PasswordSalt', - onSuccess: (resp) => { - const passwordSalt = resp?.passwordSalt; - - switch (options.reason) { - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - activate(options, passwordSalt); - break; - case WebSocketConnectReason.PASSWORD_RESET: - forgotPasswordReset(options, passwordSalt); - break; - default: - login(options, passwordSalt); - } - }, - onResponseCode: { - [ProtoController.root.Response.ResponseCode.RespRegistrationRequired]: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required'); - onFailure(); - }, - }, - onError: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Login failed: Unknown Reason'); - onFailure(); - }, - }); -} diff --git a/webclient/src/websocket/commands/session/updateStatus.ts b/webclient/src/websocket/commands/session/updateStatus.ts deleted file mode 100644 index eddd774f8..000000000 --- a/webclient/src/websocket/commands/session/updateStatus.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { StatusEnum } from 'types'; -import webClient from '../../WebClient'; -import { SessionPersistence } from '../../persistence'; - -export function updateStatus(status: StatusEnum, description: string): void { - SessionPersistence.updateStatus(status, description); - - webClient.updateStatus(status); -} diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts deleted file mode 100644 index 77a325c1e..000000000 --- a/webclient/src/websocket/events/common/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; -import { playerPropertiesChanged } from './playerPropertiesChanged'; - -export const CommonEvents: ProtobufEvents = { - '.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged, -} diff --git a/webclient/src/websocket/events/common/playerPropertiesChanged.ts b/webclient/src/websocket/events/common/playerPropertiesChanged.ts deleted file mode 100644 index 557e57ac9..000000000 --- a/webclient/src/websocket/events/common/playerPropertiesChanged.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PlayerGamePropertiesData } from '../session/interfaces'; -import { SessionPersistence } from '../../persistence'; - -export function playerPropertiesChanged(payload: PlayerGamePropertiesData): void { - SessionPersistence.playerPropertiesChanged(payload); -} diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts deleted file mode 100644 index a7b3277a5..000000000 --- a/webclient/src/websocket/events/game/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; -import { joinGame } from './joinGame'; -import { leaveGame } from './leaveGame'; - - -export const GameEvents: ProtobufEvents = { - '.Event_Join.ext': joinGame, - '.Event_Leave.ext': leaveGame, - '.Event_GameClosed.ext': () => console.log('Event_GameClosed.ext'), - '.Event_GameHostChanged.ext': () => console.log('Event_GameHostChanged.ext'), - '.Event_Kicked.ext': () => console.log('Event_Kicked.ext'), - '.Event_GameStateChanged.ext': () => console.log('Event_GameStateChanged.ext'), - // '.Event_PlayerPropertiesChanged.ext': () => console.log("Event_PlayerProperties.ext"), - '.Event_GameSay.ext': () => console.log('Event_GameSay.ext'), - '.Event_CreateArrow.ext': () => console.log('Event_CreateArrow.ext'), - '.Event_DeleteArrow.ext': () => console.log('Event_DeleteArrow.ext'), - '.Event_CreateCounter.ext': () => console.log('Event_CreateCounter.ext'), - '.Event_SetCounter.ext': () => console.log('Event_SetCounter.ext'), - '.Event_DelCounter.ext': () => console.log('Event_DelCounter.ext'), - '.Event_DrawCards.ext': () => console.log('Event_DrawCards.ext'), - '.Event_RevealCards.ext': () => console.log('Event_RevealCards.ext'), - '.Event_Shuffle.ext': () => console.log('Event_Shuffle.ext'), - '.Event_RollDie.ext': () => console.log('Event_Roll.ext'), - '.Event_MoveCard.ext': () => console.log('Event_MoveCard.ext'), - '.Event_FlipCard.ext': () => console.log('Event_FlipCard.ext'), - '.Event_DestroyCard.ext': () => console.log('Event_DestroyCard.ext'), - '.Event_AttachCard.ext': () => console.log('Event_AttachCard.ext'), - '.Event_CreateToken.ext': () => console.log('Event_CreateToken.ext'), - '.Event_SetCardAttribute.ext': () => console.log('Event_SetCardAttribute.ext'), - '.Event_SetCardCounter.ext': () => console.log('Event_SetCardCounter.ext'), - '.Event_SetActivePlayer.ext': () => console.log('Event_SetActivePlayer.ext'), - '.Event_SetActivePhase.ext': () => console.log('Event_SetActivePhase.ext'), - '.Event_DumpZone.ext': () => console.log('Event_DumpZone.ext'), - '.Event_ChangeZoneProperties.ext': () => console.log('Event_ChangeZoneProperties.ext'), - '.Event_ReverseTurn.ext': () => console.log('Event_ReverseTurn.ext'), -}; diff --git a/webclient/src/websocket/events/game/joinGame.ts b/webclient/src/websocket/events/game/joinGame.ts deleted file mode 100644 index 30cef87dc..000000000 --- a/webclient/src/websocket/events/game/joinGame.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { GamePersistence } from '../../persistence'; -import { PlayerGamePropertiesData } from '../session/interfaces'; - -export function joinGame(playerGamePropertiesData: PlayerGamePropertiesData): void { - GamePersistence.joinGame(playerGamePropertiesData); -} diff --git a/webclient/src/websocket/events/game/leaveGame.ts b/webclient/src/websocket/events/game/leaveGame.ts deleted file mode 100644 index 74a4dc6c5..000000000 --- a/webclient/src/websocket/events/game/leaveGame.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LeaveGameReason } from 'types'; -import { GamePersistence } from '../../persistence'; - - -export function leaveGame(reason: LeaveGameReason): void { - GamePersistence.leaveGame(reason); -} diff --git a/webclient/src/websocket/events/index.ts b/webclient/src/websocket/events/index.ts deleted file mode 100644 index ed74f9b93..000000000 --- a/webclient/src/websocket/events/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './common'; -export * from './room'; -export * from './session'; -export * from './game'; diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts deleted file mode 100644 index 5b571d388..000000000 --- a/webclient/src/websocket/events/room/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; - -import { joinRoom } from './joinRoom'; -import { leaveRoom } from './leaveRoom'; -import { listGames } from './listGames'; -import { roomSay } from './roomSay'; -import { removeMessages } from './removeMessages'; - -export const RoomEvents: ProtobufEvents = { - '.Event_JoinRoom.ext': joinRoom, - '.Event_LeaveRoom.ext': leaveRoom, - '.Event_ListGames.ext': listGames, - '.Event_RemoveMessages.ext': removeMessages, - '.Event_RoomSay.ext': roomSay, -}; diff --git a/webclient/src/websocket/events/room/interfaces.ts b/webclient/src/websocket/events/room/interfaces.ts deleted file mode 100644 index b3f922141..000000000 --- a/webclient/src/websocket/events/room/interfaces.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Game, User } from 'types'; - -export interface JoinRoomData { - userInfo: User; -} - -export interface LeaveRoomData { - name: string; -} - -export interface ListGamesData { - gameList: Game[]; -} - -export interface RemoveMessagesData { - name: string; - amount: number; -} - -export interface RoomEvent { - roomEvent: { - roomId: number; - } -} diff --git a/webclient/src/websocket/events/room/joinRoom.ts b/webclient/src/websocket/events/room/joinRoom.ts deleted file mode 100644 index b1a5f6606..000000000 --- a/webclient/src/websocket/events/room/joinRoom.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RoomPersistence } from '../../persistence'; -import { JoinRoomData, RoomEvent } from './interfaces'; - -export function joinRoom({ userInfo }: JoinRoomData, { roomEvent: { roomId } }: RoomEvent): void { - RoomPersistence.userJoined(roomId, userInfo); -} diff --git a/webclient/src/websocket/events/room/leaveRoom.ts b/webclient/src/websocket/events/room/leaveRoom.ts deleted file mode 100644 index 6d45197fc..000000000 --- a/webclient/src/websocket/events/room/leaveRoom.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RoomPersistence } from '../../persistence'; -import { LeaveRoomData, RoomEvent } from './interfaces'; - -export function leaveRoom({ name }: LeaveRoomData, { roomEvent: { roomId } }: RoomEvent): void { - RoomPersistence.userLeft(roomId, name); -} diff --git a/webclient/src/websocket/events/room/listGames.ts b/webclient/src/websocket/events/room/listGames.ts deleted file mode 100644 index d460a5336..000000000 --- a/webclient/src/websocket/events/room/listGames.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RoomPersistence } from '../../persistence'; -import { ListGamesData, RoomEvent } from './interfaces'; - -export function listGames({ gameList }: ListGamesData, { roomEvent: { roomId } }: RoomEvent): void { - RoomPersistence.updateGames(roomId, gameList); -} diff --git a/webclient/src/websocket/events/room/removeMessages.ts b/webclient/src/websocket/events/room/removeMessages.ts deleted file mode 100644 index 4fe01cb6f..000000000 --- a/webclient/src/websocket/events/room/removeMessages.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RoomPersistence } from '../../persistence'; -import { RemoveMessagesData, RoomEvent } from './interfaces'; - -export function removeMessages({ name, amount }: RemoveMessagesData, { roomEvent: { roomId } }: RoomEvent): void { - RoomPersistence.removeMessages(roomId, name, amount); -} diff --git a/webclient/src/websocket/events/room/roomSay.ts b/webclient/src/websocket/events/room/roomSay.ts deleted file mode 100644 index 5a96198ea..000000000 --- a/webclient/src/websocket/events/room/roomSay.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Message } from 'types'; - -import { RoomPersistence } from '../../persistence'; -import { RoomEvent } from './interfaces'; - -export function roomSay(message: Message, { roomEvent: { roomId } }: RoomEvent): void { - RoomPersistence.addMessage(roomId, message); -} diff --git a/webclient/src/websocket/events/session/addToList.ts b/webclient/src/websocket/events/session/addToList.ts deleted file mode 100644 index 08b19b45d..000000000 --- a/webclient/src/websocket/events/session/addToList.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { AddToListData } from './interfaces'; - -export function addToList({ listName, userInfo }: AddToListData): void { - switch (listName) { - case 'buddy': { - SessionPersistence.addToBuddyList(userInfo); - break; - } - case 'ignore': { - SessionPersistence.addToIgnoreList(userInfo); - break; - } - default: { - console.log(`Attempted to add to unknown list: ${listName}`); - } - } -} diff --git a/webclient/src/websocket/events/session/connectionClosed.ts b/webclient/src/websocket/events/session/connectionClosed.ts deleted file mode 100644 index 227113059..000000000 --- a/webclient/src/websocket/events/session/connectionClosed.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { StatusEnum } from 'types'; -import { ProtoController } from '../../services/ProtoController'; -import { updateStatus } from '../../commands/session'; -import { ConnectionClosedData } from './interfaces'; - -export function connectionClosed({ reason, reasonStr }: ConnectionClosedData): void { - let message: string; - - // @TODO (5) - if (reasonStr) { - message = reasonStr; - } else { - const { CloseReason } = ProtoController.root.Event_ConnectionClosed; - switch (reason) { - case CloseReason.USER_LIMIT_REACHED: - message = 'The server has reached its maximum user capacity'; - break; - case CloseReason.TOO_MANY_CONNECTIONS: - message = 'There are too many concurrent connections from your address'; - break; - case CloseReason.BANNED: - message = 'You are banned'; - break; - case CloseReason.DEMOTED: - message = 'You were demoted'; - break; - case CloseReason.SERVER_SHUTDOWN: - message = 'Scheduled server shutdown'; - break; - case CloseReason.USERNAMEINVALID: - message = 'Invalid username'; - break; - case CloseReason.LOGGEDINELSEWERE: - message = 'You have been logged out due to logging in at another location'; - break; - case CloseReason.OTHER: - default: - message = 'Unknown reason'; - break; - } - } - - updateStatus(StatusEnum.DISCONNECTED, message); -} diff --git a/webclient/src/websocket/events/session/gameJoined.ts b/webclient/src/websocket/events/session/gameJoined.ts deleted file mode 100644 index 8c0d49006..000000000 --- a/webclient/src/websocket/events/session/gameJoined.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { GameJoinedData } from './interfaces'; - -export function gameJoined(gameJoined: GameJoinedData): void { - SessionPersistence.gameJoined(gameJoined); -} diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts deleted file mode 100644 index 5b3ab198e..000000000 --- a/webclient/src/websocket/events/session/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; -import { addToList } from './addToList'; -import { connectionClosed } from './connectionClosed'; -import { listRooms } from './listRooms'; -import { notifyUser } from './notifyUser'; -import { removeFromList } from './removeFromList'; -import { replayAdded } from './replayAdded'; -import { serverCompleteList } from './serverCompleteList'; -import { serverIdentification } from './serverIdentification'; -import { serverMessage } from './serverMessage'; -import { serverShutdown } from './serverShutdown'; -import { userJoined } from './userJoined'; -import { userLeft } from './userLeft'; -import { userMessage } from './userMessage'; -import { gameJoined } from './gameJoined'; - -export const SessionEvents: ProtobufEvents = { - '.Event_AddToList.ext': addToList, - '.Event_ConnectionClosed.ext': connectionClosed, - '.Event_GameJoined.ext': gameJoined, - '.Event_ListRooms.ext': listRooms, - '.Event_NotifyUser.ext': notifyUser, - '.Event_RemoveFromList.ext': removeFromList, - '.Event_ReplayAdded.ext': replayAdded, - '.Event_ServerCompleteList.ext': serverCompleteList, - '.Event_ServerIdentification.ext': serverIdentification, - '.Event_ServerMessage.ext': serverMessage, - '.Event_ServerShutdown.ext': serverShutdown, - '.Event_UserJoined.ext': userJoined, - '.Event_UserLeft.ext': userLeft, - '.Event_UserMessage.ext': userMessage, -} diff --git a/webclient/src/websocket/events/session/interfaces.ts b/webclient/src/websocket/events/session/interfaces.ts deleted file mode 100644 index ddc10d103..000000000 --- a/webclient/src/websocket/events/session/interfaces.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Game, NotificationType, ReplayMatch, Room, User } from 'types'; - -export interface AddToListData { - listName: string; - userInfo: User; -} - -export interface ConnectionClosedData { - endTime: number; - reason: number; - reasonStr: string; -} - -export interface GameJoinedData { - gameInfo: Game; - gameTypes: any[]; - hostId: number; - playerId: number; - spectator: boolean; - resuming: boolean; - judge: boolean; -} - -export interface ListRoomsData { - roomList: Room[]; -} - -export interface NotifyUserData { - type: NotificationType; - warningReason: string; - customTitle: string; - customContent: string; -} - -export interface PlayerGamePropertiesData { - playerId: number; - userInfo: User; - spectator: boolean; - conceded: boolean; - readyStart: boolean; - deckHash: string; - pingSeconds: number; - sideboardLocked: boolean; - judge: boolean; -} - -export interface RemoveFromListData { - listName: string; - userName: string; -} - -export interface ServerIdentificationData { - protocolVersion: number; - serverName: string; - serverVersion: string; - serverOptions: number; -} - -export interface ServerMessageData { - message: string; -} - -export interface ServerShutdownData { - reason: string; - minutes: number; -} - -export interface UserJoinedData { - userInfo: User; -} - -export interface UserLeftData { - name: string; -} - -export interface UserMessageData { - senderName: string; - receiverName: string; - message: string; -} - -export interface ReplayAddedData { - matchInfo: ReplayMatch; -} - -export interface ServerCompleteListData { - serverId: number; - userList: User[]; - roomList: Room[]; -} diff --git a/webclient/src/websocket/events/session/listRooms.ts b/webclient/src/websocket/events/session/listRooms.ts deleted file mode 100644 index faba2968e..000000000 --- a/webclient/src/websocket/events/session/listRooms.ts +++ /dev/null @@ -1,16 +0,0 @@ -import webClient from '../../WebClient'; -import { joinRoom } from '../../commands/session'; -import { RoomPersistence } from '../../persistence'; -import { ListRoomsData } from './interfaces'; - -export function listRooms({ roomList }: ListRoomsData): void { - RoomPersistence.updateRooms(roomList); - - if (webClient.clientOptions.autojoinrooms) { - roomList.forEach(({ autoJoin, roomId }) => { - if (autoJoin) { - joinRoom(roomId); - } - }); - } -} diff --git a/webclient/src/websocket/events/session/notifyUser.ts b/webclient/src/websocket/events/session/notifyUser.ts deleted file mode 100644 index f5673fe3f..000000000 --- a/webclient/src/websocket/events/session/notifyUser.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { NotifyUserData } from './interfaces'; - - -export function notifyUser(payload: NotifyUserData): void { - SessionPersistence.notifyUser(payload); -} diff --git a/webclient/src/websocket/events/session/removeFromList.ts b/webclient/src/websocket/events/session/removeFromList.ts deleted file mode 100644 index 20e2d7f54..000000000 --- a/webclient/src/websocket/events/session/removeFromList.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { RemoveFromListData } from './interfaces'; - -export function removeFromList({ listName, userName }: RemoveFromListData): void { - switch (listName) { - case 'buddy': { - SessionPersistence.removeFromBuddyList(userName); - break; - } - case 'ignore': { - SessionPersistence.removeFromIgnoreList(userName); - break; - } - default: { - console.log(`Attempted to remove from unknown list: ${listName}`); - } - } -} diff --git a/webclient/src/websocket/events/session/replayAdded.ts b/webclient/src/websocket/events/session/replayAdded.ts deleted file mode 100644 index 18a4ea82d..000000000 --- a/webclient/src/websocket/events/session/replayAdded.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { ReplayAddedData } from './interfaces'; - -export function replayAdded({ matchInfo }: ReplayAddedData): void { - SessionPersistence.replayAdded(matchInfo); -} diff --git a/webclient/src/websocket/events/session/serverCompleteList.ts b/webclient/src/websocket/events/session/serverCompleteList.ts deleted file mode 100644 index 77d37a31e..000000000 --- a/webclient/src/websocket/events/session/serverCompleteList.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RoomPersistence, SessionPersistence } from '../../persistence'; -import { ServerCompleteListData } from './interfaces'; - -export function serverCompleteList({ userList, roomList }: ServerCompleteListData): void { - SessionPersistence.updateUsers(userList); - RoomPersistence.updateRooms(roomList); -} diff --git a/webclient/src/websocket/events/session/serverIdentification.ts b/webclient/src/websocket/events/session/serverIdentification.ts deleted file mode 100644 index 87ae79453..000000000 --- a/webclient/src/websocket/events/session/serverIdentification.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; - -import webClient from '../../WebClient'; -import { - activate, - disconnect, - login, - register, - requestPasswordSalt, - forgotPasswordChallenge, - forgotPasswordRequest, - forgotPasswordReset, - updateStatus, -} from '../../commands/session'; -import { generateSalt, passwordSaltSupported } from '../../utils'; -import { ServerIdentificationData } from './interfaces'; -import { SessionPersistence } from '../../persistence'; - -export function serverIdentification(info: ServerIdentificationData): void { - const { serverName, serverVersion, protocolVersion, serverOptions } = info; - if (protocolVersion !== webClient.protocolVersion) { - updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`); - disconnect(); - return; - } - - const getPasswordSalt = passwordSaltSupported(serverOptions); - const connectOptions = { ...webClient.options }; - - switch (connectOptions.reason) { - case WebSocketConnectReason.LOGIN: - updateStatus(StatusEnum.LOGGING_IN, 'Logging In...'); - if (getPasswordSalt) { - requestPasswordSalt(connectOptions); - } else { - login(connectOptions); - } - break; - case WebSocketConnectReason.REGISTER: - const passwordSalt = getPasswordSalt ? generateSalt() : null; - register(connectOptions, passwordSalt); - break; - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - if (getPasswordSalt) { - requestPasswordSalt(connectOptions); - } else { - activate(connectOptions); - } - break; - case WebSocketConnectReason.PASSWORD_RESET_REQUEST: - forgotPasswordRequest(connectOptions); - break; - case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: - forgotPasswordChallenge(connectOptions); - break; - case WebSocketConnectReason.PASSWORD_RESET: - if (getPasswordSalt) { - requestPasswordSalt(connectOptions); - } else { - forgotPasswordReset(connectOptions); - } - break; - default: - updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Reason: ' + connectOptions.reason); - disconnect(); - break; - } - - webClient.options = {} as WebSocketConnectOptions; - SessionPersistence.updateInfo(serverName, serverVersion); -} diff --git a/webclient/src/websocket/events/session/serverMessage.ts b/webclient/src/websocket/events/session/serverMessage.ts deleted file mode 100644 index f9e52aa7a..000000000 --- a/webclient/src/websocket/events/session/serverMessage.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { ServerMessageData } from './interfaces'; - -export function serverMessage({ message }: ServerMessageData): void { - SessionPersistence.serverMessage(message); -} diff --git a/webclient/src/websocket/events/session/serverShutdown.ts b/webclient/src/websocket/events/session/serverShutdown.ts deleted file mode 100644 index cdda893a4..000000000 --- a/webclient/src/websocket/events/session/serverShutdown.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { ServerShutdownData } from './interfaces'; - - -export function serverShutdown(payload: ServerShutdownData): void { - SessionPersistence.serverShutdown(payload); -} diff --git a/webclient/src/websocket/events/session/userJoined.ts b/webclient/src/websocket/events/session/userJoined.ts deleted file mode 100644 index cb512db60..000000000 --- a/webclient/src/websocket/events/session/userJoined.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { UserJoinedData } from './interfaces'; - -export function userJoined({ userInfo }: UserJoinedData): void { - SessionPersistence.userJoined(userInfo); -} diff --git a/webclient/src/websocket/events/session/userLeft.ts b/webclient/src/websocket/events/session/userLeft.ts deleted file mode 100644 index 9e00e59e1..000000000 --- a/webclient/src/websocket/events/session/userLeft.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { UserLeftData } from './interfaces'; - -export function userLeft({ name }: UserLeftData): void { - SessionPersistence.userLeft(name); -} diff --git a/webclient/src/websocket/events/session/userMessage.ts b/webclient/src/websocket/events/session/userMessage.ts deleted file mode 100644 index bff08460b..000000000 --- a/webclient/src/websocket/events/session/userMessage.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SessionPersistence } from '../../persistence'; -import { UserMessageData } from './interfaces'; - - - -export function userMessage(payload: UserMessageData): void { - SessionPersistence.userMessage(payload); -} diff --git a/webclient/src/websocket/index.ts b/webclient/src/websocket/index.ts deleted file mode 100644 index b52c97168..000000000 --- a/webclient/src/websocket/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './commands'; - -export { default as webClient } from './WebClient'; diff --git a/webclient/src/websocket/persistence/AdminPersistence.ts b/webclient/src/websocket/persistence/AdminPersistence.ts deleted file mode 100644 index 9552d8abf..000000000 --- a/webclient/src/websocket/persistence/AdminPersistence.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ServerDispatch } from 'store'; - -export class AdminPersistence { - static adjustMod(userName: string, shouldBeMod: boolean, shouldBeJudge: boolean) { - ServerDispatch.adjustMod(userName, shouldBeMod, shouldBeJudge) - } - - static reloadConfig() { - ServerDispatch.reloadConfig(); - } - - static shutdownServer() { - ServerDispatch.shutdownServer(); - } - - static updateServerMessage() { - ServerDispatch.updateServerMessage(); - } -} diff --git a/webclient/src/websocket/persistence/GamePersistence.ts b/webclient/src/websocket/persistence/GamePersistence.ts deleted file mode 100644 index e39c1b5a9..000000000 --- a/webclient/src/websocket/persistence/GamePersistence.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PlayerGamePropertiesData } from '../events/session/interfaces'; -import { LeaveGameReason } from '../../types'; - -export class GamePersistence { - static joinGame(playerGamePropertiesData: PlayerGamePropertiesData) { - console.log('joinGame', playerGamePropertiesData); - } - - static leaveGame(reason: LeaveGameReason) { - console.log('leaveGame', reason); - } -} diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.ts b/webclient/src/websocket/persistence/ModeratorPersistence.ts deleted file mode 100644 index d1b991fa0..000000000 --- a/webclient/src/websocket/persistence/ModeratorPersistence.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ServerDispatch } from 'store'; -import { BanHistoryItem, LogItem, WarnHistoryItem, WarnListItem } from 'types'; - -import NormalizeService from '../utils/NormalizeService'; - -export class ModeratorPersistence { - static banFromServer(userName: string): void { - ServerDispatch.banFromServer(userName); - } - - static banHistory(userName: string, banHistory: BanHistoryItem[]): void { - ServerDispatch.banHistory(userName, banHistory); - } - - static viewLogs(logs: LogItem[]): void { - ServerDispatch.viewLogs(NormalizeService.normalizeLogs(logs)); - } - - static warnHistory(userName: string, warnHistory: WarnHistoryItem[]): void { - ServerDispatch.warnHistory(userName, warnHistory); - } - - static warnListOptions(warnList: WarnListItem[]): void { - ServerDispatch.warnListOptions(warnList); - } - - static warnUser(userName: string): void { - ServerDispatch.warnUser(userName); - } - - static grantReplayAccess(replayId: number, moderatorName: string): void { - ServerDispatch.grantReplayAccess(replayId, moderatorName); - } - - static forceActivateUser(usernameToActivate: string, moderatorName: string): void { - ServerDispatch.forceActivateUser(usernameToActivate, moderatorName); - } - - static getAdminNotes(userName: string, notes: string): void { - ServerDispatch.getAdminNotes(userName, notes); - } - - static updateAdminNotes(userName: string, notes: string): void { - ServerDispatch.updateAdminNotes(userName, notes); - } -} diff --git a/webclient/src/websocket/persistence/RoomPersistence.ts b/webclient/src/websocket/persistence/RoomPersistence.ts deleted file mode 100644 index a69e79338..000000000 --- a/webclient/src/websocket/persistence/RoomPersistence.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { store, RoomsDispatch, RoomsSelectors } from 'store'; -import { Game, Message, Room, User } from 'types'; -import NormalizeService from '../utils/NormalizeService'; - -export class RoomPersistence { - static clearStore() { - RoomsDispatch.clearStore(); - } - - static joinRoom(roomInfo: Room) { - NormalizeService.normalizeRoomInfo(roomInfo); - RoomsDispatch.joinRoom(roomInfo); - } - - static leaveRoom(roomId: number) { - RoomsDispatch.leaveRoom(roomId); - } - - static updateRooms(rooms: Room[]) { - RoomsDispatch.updateRooms(rooms); - } - - static updateGames(roomId: number, gameList: Game[]) { - const game = gameList[0]; - - if (!game.gameType) { - const room = RoomsSelectors.getRoom(store.getState(), roomId); - - if (room) { - const { gametypeMap } = room; - NormalizeService.normalizeGameObject(game, gametypeMap); - } - } - - RoomsDispatch.updateGames(roomId, gameList); - } - - static addMessage(roomId: number, message: Message) { - NormalizeService.normalizeUserMessage(message); - - RoomsDispatch.addMessage(roomId, message); - } - - static userJoined(roomId: number, user: User) { - RoomsDispatch.userJoined(roomId, user); - } - - static userLeft(roomId: number, name: string) { - RoomsDispatch.userLeft(roomId, name); - } - - static removeMessages(roomId: number, name: string, amount: number): void { - RoomsDispatch.removeMessages(roomId, name, amount); - }; - - static gameCreated(roomId: number) { - RoomsDispatch.gameCreated(roomId); - } - - static joinedGame(roomId: number, gameId: number) { - RoomsDispatch.joinedGame(roomId, gameId); - } -} diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts deleted file mode 100644 index 9962af968..000000000 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { ServerDispatch } from 'store'; -import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types'; - -import { sanitizeHtml } from 'websocket/utils'; -import { - GameJoinedData, - NotifyUserData, - PlayerGamePropertiesData, - ServerShutdownData, - UserMessageData -} from '../events/session/interfaces'; -import NormalizeService from '../utils/NormalizeService'; - -export class SessionPersistence { - static initialized() { - ServerDispatch.initialized(); - } - - static clearStore() { - ServerDispatch.clearStore(); - } - - static loginSuccessful(options: WebSocketConnectOptions) { - ServerDispatch.loginSuccessful(options); - } - - static loginFailed() { - ServerDispatch.loginFailed(); - } - - static connectionClosed(reason: number) { - ServerDispatch.connectionClosed(reason); - } - - static connectionFailed() { - ServerDispatch.connectionFailed(); - } - - static testConnectionSuccessful() { - ServerDispatch.testConnectionSuccessful(); - } - - static testConnectionFailed() { - ServerDispatch.testConnectionFailed(); - } - - static updateBuddyList(buddyList) { - ServerDispatch.updateBuddyList(buddyList); - } - - static addToBuddyList(user: User) { - ServerDispatch.addToBuddyList(user); - } - - static removeFromBuddyList(userName: string) { - ServerDispatch.removeFromBuddyList(userName); - } - - static updateIgnoreList(ignoreList) { - ServerDispatch.updateIgnoreList(ignoreList); - } - - static addToIgnoreList(user: User) { - ServerDispatch.addToIgnoreList(user); - } - - static removeFromIgnoreList(userName: string) { - ServerDispatch.removeFromIgnoreList(userName); - } - - static updateInfo(name: string, version: string) { - ServerDispatch.updateInfo(name, version); - } - - static updateStatus(state: number, description: string) { - ServerDispatch.updateStatus(state, description); - - if (state === StatusEnum.DISCONNECTED) { - this.connectionClosed(state); - } - } - - static updateUser(user: User) { - ServerDispatch.updateUser(user); - } - - static updateUsers(users: User[]) { - ServerDispatch.updateUsers(users); - } - - static userJoined(user: User) { - ServerDispatch.userJoined(user); - } - - static userLeft(userName: string) { - ServerDispatch.userLeft(userName); - } - - static serverMessage(message: string) { - ServerDispatch.serverMessage(sanitizeHtml(message)); - } - - static accountAwaitingActivation(options: WebSocketConnectOptions) { - ServerDispatch.accountAwaitingActivation(options); - } - - static accountActivationSuccess() { - ServerDispatch.accountActivationSuccess(); - } - - static accountActivationFailed() { - ServerDispatch.accountActivationFailed(); - } - - static registrationRequiresEmail() { - ServerDispatch.registrationRequiresEmail(); - } - - static registrationSuccess() { - ServerDispatch.registrationSuccess(); - } - - static registrationFailed(reason: string, endTime?: number) { - const reasonMsg = endTime ? NormalizeService.normalizeBannedUserError(reason, endTime) : reason; - - ServerDispatch.registrationFailed(reasonMsg); - } - - static registrationEmailError(error: string) { - ServerDispatch.registrationEmailError(error); - } - - static registrationPasswordError(error: string) { - ServerDispatch.registrationPasswordError(error); - } - - static registrationUserNameError(error: string) { - ServerDispatch.registrationUserNameError(error); - } - - static resetPasswordChallenge() { - ServerDispatch.resetPasswordChallenge(); - } - - static resetPassword() { - ServerDispatch.resetPassword(); - } - - static resetPasswordSuccess() { - ServerDispatch.resetPasswordSuccess(); - } - - static resetPasswordFailed() { - ServerDispatch.resetPasswordFailed(); - } - - static accountPasswordChange(): void { - ServerDispatch.accountPasswordChange(); - } - - static accountEditChanged(realName?: string, email?: string, country?: string): void { - ServerDispatch.accountEditChanged({ realName, email, country }); - } - - static accountImageChanged(avatarBmp: Uint8Array): void { - ServerDispatch.accountImageChanged({ avatarBmp }); - } - - static directMessageSent(userName: string, message: string): void { - ServerDispatch.directMessageSent(userName, message); - } - - static getUserInfo(userInfo: User) { - ServerDispatch.getUserInfo(userInfo); - } - - static getGamesOfUser(userName: string, response: any): void { - // Response_GetGamesOfUser contains a gameList field — log for now until game layer is complete - console.log('getGamesOfUser', userName, response); - } - - static gameJoined(gameJoinedData: GameJoinedData): void { - console.log('gameJoined', gameJoinedData); - } - - static notifyUser(notification: NotifyUserData): void { - ServerDispatch.notifyUser(notification); - } - - static playerPropertiesChanged(payload: PlayerGamePropertiesData): void { - console.log('playerPropertiesChanged', payload); - } - - static serverShutdown(data: ServerShutdownData): void { - ServerDispatch.serverShutdown(data); - } - - static userMessage(messageData: UserMessageData): void { - ServerDispatch.userMessage(messageData); - } - - static addToList(list: string, userName: string): void { - ServerDispatch.addToList(list, userName) - } - - static removeFromList(list: string, userName: string): void { - ServerDispatch.removeFromList(list, userName); - } - - static deleteServerDeck(deckId: number): void { - ServerDispatch.deckDelete(deckId); - } - - static updateServerDecks(deckList: DeckList): void { - ServerDispatch.backendDecks(deckList); - } - - static uploadServerDeck(path: string, treeItem: DeckStorageTreeItem): void { - ServerDispatch.deckUpload(path, treeItem); - } - - static createServerDeckDir(path: string, dirName: string): void { - ServerDispatch.deckNewDir(path, dirName); - } - - static deleteServerDeckDir(path: string): void { - ServerDispatch.deckDelDir(path); - } - - static replayList(matchList: ReplayMatch[]): void { - ServerDispatch.replayList(matchList); - } - - static replayAdded(matchInfo: ReplayMatch): void { - ServerDispatch.replayAdded(matchInfo); - } - - static replayModifyMatch(gameId: number, doNotHide: boolean): void { - ServerDispatch.replayModifyMatch(gameId, doNotHide); - } - - static replayDeleteMatch(gameId: number): void { - ServerDispatch.replayDeleteMatch(gameId); - } -} - - diff --git a/webclient/src/websocket/persistence/index.ts b/webclient/src/websocket/persistence/index.ts deleted file mode 100644 index a1e34fffa..000000000 --- a/webclient/src/websocket/persistence/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { AdminPersistence } from './AdminPersistence'; -export { RoomPersistence } from './RoomPersistence'; -export { SessionPersistence } from './SessionPersistence'; -export { ModeratorPersistence } from './ModeratorPersistence'; -export { GamePersistence } from './GamePersistence'; diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts deleted file mode 100644 index fce741d35..000000000 --- a/webclient/src/websocket/services/BackendService.ts +++ /dev/null @@ -1,82 +0,0 @@ -import webClient from '../WebClient'; -import { ProtoController } from './ProtoController'; - -export interface CommandOptions { - responseName?: string; - onSuccess?: (response: any, raw: any) => void; - onError?: (responseCode: number, raw: any) => void; - onResponseCode?: { [code: number]: (raw: any) => void }; - onResponse?: (raw: any) => void; -} - -export class BackendService { - static sendSessionCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const sc = ProtoController.root.SessionCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendSessionCommand(sc, raw => { - BackendService.handleResponse(commandName, raw, options); - }); - } - - static sendRoomCommand(roomId: number, commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const rc = ProtoController.root.RoomCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendRoomCommand(roomId, rc, raw => { - BackendService.handleResponse(commandName, raw, options); - }); - } - - static sendModeratorCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const mc = ProtoController.root.ModeratorCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendModeratorCommand(mc, raw => { - BackendService.handleResponse(commandName, raw, options); - }); - } - - static sendAdminCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const ac = ProtoController.root.AdminCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendAdminCommand(ac, raw => { - BackendService.handleResponse(commandName, raw, options); - }); - } - - private static handleResponse(commandName: string, raw: any, options: CommandOptions): void { - if (options.onResponse) { - options.onResponse(raw); - return; - } - - const { responseCode } = raw; - - if (responseCode === ProtoController.root.Response.ResponseCode.RespOk) { - if (options.onSuccess) { - const response = options.responseName - ? raw[`.${options.responseName}.ext`] - : raw; - options.onSuccess(response, raw); - } - return; - } - - if (options.onResponseCode?.[responseCode]) { - options.onResponseCode[responseCode](raw); - return; - } - - if (options.onError) { - options.onError(responseCode, raw); - } else { - console.error(`${commandName} failed with response code: ${responseCode}`); - } - } -} diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts deleted file mode 100644 index df5169b4c..000000000 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { KeepAliveService } from './KeepAliveService'; - -import webClient from '../WebClient'; - -describe('KeepAliveService', () => { - let service: KeepAliveService; - - beforeEach(() => { - jest.useFakeTimers(); - - service = new KeepAliveService(webClient.socket); - }); - - it('should create', () => { - expect(service).toBeDefined(); - }); - - describe('startPingLoop', () => { - let resolvePing; - let interval; - let promise; - let ping; - let checkReadyStateSpy; - - beforeEach(() => { - interval = 100; - promise = new Promise(resolve => resolvePing = resolve); - ping = (done) => promise.then(done); - - checkReadyStateSpy = jest.spyOn(webClient.socket, 'checkReadyState'); - checkReadyStateSpy.mockImplementation(() => true); - - service.startPingLoop(interval, ping); - jest.advanceTimersByTime(interval); - }); - - it('should start ping loop', () => { - expect((service as any).keepalivecb).toBeDefined(); - expect((service as any).lastPingPending).toBeTruthy(); - }); - - it('should call ping callback when done', (done: jest.DoneCallback) => { - resolvePing(); - - promise.then(() => { - expect((service as any).lastPingPending).toBeFalsy(); - done(); - }); - }); - - it('should fire disconnected$ if lastPingPending is still true', () => { - jest.spyOn(service.disconnected$, 'next').mockImplementation(() => {}); - jest.advanceTimersByTime(interval); - - expect(service.disconnected$.next).toHaveBeenCalled(); - }); - - it('should endPingLoop if socket is not open', () => { - jest.spyOn(service, 'endPingLoop').mockImplementation(() => {}); - checkReadyStateSpy.mockImplementation(() => false); - - resolvePing(); - jest.advanceTimersByTime(interval); - - expect(service.endPingLoop).toHaveBeenCalled(); - }); - }); -}); diff --git a/webclient/src/websocket/services/KeepAliveService.ts b/webclient/src/websocket/services/KeepAliveService.ts deleted file mode 100644 index 070a532e6..000000000 --- a/webclient/src/websocket/services/KeepAliveService.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Subject } from 'rxjs'; - -import { WebSocketService } from './WebSocketService'; - -export class KeepAliveService { - private socket: WebSocketService; - - private keepalivecb: NodeJS.Timeout; - private lastPingPending: boolean; - - public disconnected$ = new Subject(); - - constructor(socket: WebSocketService) { - this.socket = socket; - } - - public startPingLoop(interval: number, ping: Function): void { - this.keepalivecb = setInterval(() => { - // check if the previous ping got no reply - if (this.lastPingPending) { - this.disconnected$.next(); - } - - // stop the ping loop if we"re disconnected - if (!this.socket.checkReadyState(WebSocket.OPEN)) { - this.endPingLoop(); - return; - } - - this.lastPingPending = true; - ping(() => this.lastPingPending = false); - }, interval); - } - - public endPingLoop() { - clearInterval(this.keepalivecb); - this.keepalivecb = null; - this.lastPingPending = false; - } -} diff --git a/webclient/src/websocket/services/ProtoController.ts b/webclient/src/websocket/services/ProtoController.ts deleted file mode 100644 index 130d5e196..000000000 --- a/webclient/src/websocket/services/ProtoController.ts +++ /dev/null @@ -1,24 +0,0 @@ -import protobuf from 'protobufjs'; - -import { SessionPersistence } from '../persistence'; -import ProtoFiles from '../../proto-files.json'; - -const PB_FILE_DIR = `${process.env.PUBLIC_URL}/pb`; - -// Leaf module — no imports from the websocket layer other than persistence. -// Both BackendService and ProtobufService import this; neither should import -// the other for controller access, avoiding circular dependency cycles. -export const ProtoController = { - root: null as any, - - load(): void { - const files = ProtoFiles.map(file => `${PB_FILE_DIR}/${file}`); - ProtoController.root = new protobuf.Root(); - ProtoController.root.load(files, { keepCase: false }, (err: Error) => { - if (err) { - throw err; - } - SessionPersistence.initialized(); - }); - }, -}; diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts deleted file mode 100644 index 20c4e8599..000000000 --- a/webclient/src/websocket/services/ProtobufService.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { CommonEvents, GameEvents, RoomEvents, SessionEvents } from '../events'; -import { WebClient } from '../WebClient'; -import { SessionCommands } from 'websocket'; -import { ProtoController } from './ProtoController'; - -export interface ProtobufEvents { - [event: string]: Function; -} - -export class ProtobufService { - private cmdId = 0; - private pendingCommands: { [cmdId: string]: Function } = {}; - - private webClient: WebClient; - - constructor(webClient: WebClient) { - this.webClient = webClient; - ProtoController.load(); - } - - public resetCommands() { - this.cmdId = 0; - this.pendingCommands = {}; - } - - public sendRoomCommand(roomId: number, roomCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'roomId': roomId, - 'roomCommand': [roomCmd] - }); - - this.sendCommand(cmd, raw => callback && callback(raw)); - } - - public sendSessionCommand(sesCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'sessionCommand': [sesCmd] - }); - - this.sendCommand(cmd, (raw) => callback && callback(raw)); - } - - public sendModeratorCommand(modCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'moderatorCommand': [modCmd] - }); - - this.sendCommand(cmd, (raw) => callback && callback(raw)); - } - - public sendAdminCommand(adminCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'adminCommand': [adminCmd] - }); - - this.sendCommand(cmd, (raw) => callback && callback(raw)); - } - - public sendCommand(cmd: any, callback: Function) { - this.cmdId++; - - cmd['cmdId'] = this.cmdId; - this.pendingCommands[this.cmdId] = callback; - - if (this.webClient.socket.checkReadyState(WebSocket.OPEN)) { - this.webClient.socket.send(ProtoController.root.CommandContainer.encode(cmd).finish()); - } - } - - public sendKeepAliveCommand(pingReceived: Function) { - SessionCommands.ping(pingReceived); - } - - public handleMessageEvent({ data }: MessageEvent): void { - try { - const uint8msg = new Uint8Array(data); - const msg = ProtoController.root.ServerMessage.decode(uint8msg); - - if (msg) { - switch (msg.messageType) { - case ProtoController.root.ServerMessage.MessageType.RESPONSE: - this.processServerResponse(msg.response); - break; - case ProtoController.root.ServerMessage.MessageType.ROOM_EVENT: - this.processRoomEvent(msg.roomEvent, msg); - break; - case ProtoController.root.ServerMessage.MessageType.SESSION_EVENT: - this.processSessionEvent(msg.sessionEvent, msg); - break; - case ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER: - this.processGameEvent(msg.gameEvent, msg); - break; - default: - console.log(msg); - break; - } - } - } catch (err) { - console.error('Processing failed:', err); - } - } - - private processServerResponse(response: any) { - const { cmdId } = response; - - if (this.pendingCommands[cmdId]) { - this.pendingCommands[cmdId](response); - delete this.pendingCommands[cmdId]; - } - } - - private processCommonEvent(response: any, raw: any) { - this.processEvent(response, CommonEvents, raw); - } - - private processRoomEvent(response: any, raw: any) { - this.processEvent(response, RoomEvents, raw); - } - - private processSessionEvent(response: any, raw: any) { - this.processEvent(response, SessionEvents, raw); - } - - private processGameEvent(response: any, raw: any): void { - this.processEvent(response, GameEvents, raw); - } - - private processEvent(response: any, events: ProtobufEvents, raw: any) { - for (const event in events) { - const payload = response[event]; - - if (payload) { - events[event](payload, raw); - return; - } - } - } -} diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts deleted file mode 100644 index 95fa2d356..000000000 --- a/webclient/src/websocket/services/WebSocketService.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Subject } from 'rxjs'; - -import { StatusEnum, WebSocketConnectOptions } from 'types'; - -import { KeepAliveService } from './KeepAliveService'; -import { WebClient } from '../WebClient'; -import { SessionPersistence } from '../persistence'; -import { updateStatus } from '../commands/session'; - -export class WebSocketService { - private socket: WebSocket; - private testSocket: WebSocket; - - private webClient: WebClient; - private keepAliveService: KeepAliveService; - - public message$: Subject = new Subject(); - - private keepalive: number; - - constructor(webClient: WebClient) { - this.webClient = webClient; - - this.keepAliveService = new KeepAliveService(this); - this.keepAliveService.disconnected$.subscribe(() => { - this.disconnect(); - updateStatus(StatusEnum.DISCONNECTED, 'Connection timeout'); - }); - } - - public connect(options: WebSocketConnectOptions, protocol: string = 'wss'): void { - if (window.location.hostname === 'localhost') { - protocol = 'ws'; - } - - const { host, port } = options; - this.keepalive = this.webClient.clientOptions.keepalive; - - this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); - } - - public testConnect(options: WebSocketConnectOptions, protocol: string = 'wss'): void { - if (window.location.hostname === 'localhost') { - protocol = 'ws'; - } - - const { host, port } = options; - - this.testWebSocket(`${protocol}://${host}:${port}`); - } - - public disconnect(): void { - if (this.socket) { - this.socket.close(); - } - } - - public checkReadyState(state: number): boolean { - return this.socket?.readyState === state; - } - - public send(message): void { - this.socket.send(message); - } - - private createWebSocket(url: string): WebSocket { - const socket = new WebSocket(url); - socket.binaryType = 'arraybuffer'; - - const connectionTimer = setTimeout(() => socket.close(), this.keepalive); - - socket.onopen = () => { - clearTimeout(connectionTimer); - updateStatus(StatusEnum.CONNECTED, 'Connected'); - - this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => { - this.webClient.keepAlive(pingReceived); - }); - }; - - socket.onclose = () => { - // dont overwrite failure messages - if (this.webClient.status !== StatusEnum.DISCONNECTED) { - updateStatus(StatusEnum.DISCONNECTED, 'Connection Closed'); - } - - this.keepAliveService.endPingLoop(); - }; - - socket.onerror = () => { - updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed'); - SessionPersistence.connectionFailed(); - }; - - socket.onmessage = (event: MessageEvent) => { - this.message$.next(event); - } - - return socket; - } - - private testWebSocket(url: string): void { - if (this.testSocket) { - this.testSocket.onerror = null; - this.testSocket.close(); - } - - const socket = new WebSocket(url); - socket.binaryType = 'arraybuffer'; - - const connectionTimer = setTimeout(() => socket.close(), this.webClient.clientOptions.keepalive); - - socket.onopen = () => { - clearTimeout(connectionTimer); - SessionPersistence.testConnectionSuccessful(); - socket.close(); - }; - - socket.onerror = () => { - SessionPersistence.testConnectionFailed(); - }; - - socket.onclose = () => { - this.testSocket = null; - } - - this.testSocket = socket; - } -} diff --git a/webclient/src/websocket/utils/NormalizeService.ts b/webclient/src/websocket/utils/NormalizeService.ts deleted file mode 100644 index 4d9d37ba6..000000000 --- a/webclient/src/websocket/utils/NormalizeService.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Game, GametypeMap, LogItem, LogGroups, Message, Room } from 'types'; - -export default class NormalizeService { - // Flatten room gameTypes into map object - static normalizeRoomInfo(roomInfo: Room): void { - roomInfo.gametypeMap = {}; - - const { gametypeList, gametypeMap, gameList } = roomInfo; - - gametypeList.reduce((map, type) => { - map[type.gameTypeId] = type.description; - return map; - }, gametypeMap); - - gameList.forEach((game) => NormalizeService.normalizeGameObject(game, gametypeMap)); - } - - // Flatten gameTypes[] into gameType field - // Default sortable values ("" || 0 || -1) - static normalizeGameObject(game: Game, gametypeMap: GametypeMap): void { - const { gameTypes, description } = game; - const hasType = gameTypes && gameTypes.length; - game.gameType = hasType ? gametypeMap[gameTypes[0]] : ''; - - game.description = description || ''; - } - - // Flatten logs[] into object mapped by targetType (room, game, chat) - static normalizeLogs(logs: LogItem[]): LogGroups { - return logs.reduce((obj, log) => { - const { targetType } = log; - obj[targetType] = obj[targetType] || []; - obj[targetType].push(log); - return obj; - }, {} as LogGroups); - } - - // messages sent by current user dont have their username prepended - static normalizeUserMessage(message: Message): void { - const { name } = message; - - if (name) { - message.message = `${name}: ${message.message}`; - } - } - - // Banned reason string is not being exposed by the server - static normalizeBannedUserError(reasonStr: string, endTime: number): string { - let error; - - if (endTime) { - error = 'You are banned until ' + new Date(endTime).toString(); - } else { - error = 'You are permanently banned'; - } - - if (reasonStr) { - error += '\n\n' + reasonStr; - } - - return error; - } -} diff --git a/webclient/src/websocket/utils/guid.util.ts b/webclient/src/websocket/utils/guid.util.ts deleted file mode 100644 index 73b17d719..000000000 --- a/webclient/src/websocket/utils/guid.util.ts +++ /dev/null @@ -1,8 +0,0 @@ -function s4(): string { - const s4 = Math.floor((1 + Math.random()) * 0x10000); - return s4.toString(16).substring(1); -} - -export function guid(): string { - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); -} diff --git a/webclient/src/websocket/utils/index.ts b/webclient/src/websocket/utils/index.ts deleted file mode 100644 index 4147f4af8..000000000 --- a/webclient/src/websocket/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './guid.util'; -export * from './sanitizeHtml.util'; -export * from './passwordHasher'; diff --git a/webclient/src/websocket/utils/passwordHasher.ts b/webclient/src/websocket/utils/passwordHasher.ts deleted file mode 100644 index 164a91823..000000000 --- a/webclient/src/websocket/utils/passwordHasher.ts +++ /dev/null @@ -1,32 +0,0 @@ -import sha512 from 'crypto-js/sha512'; -import Base64 from 'crypto-js/enc-base64'; -import { ProtoController } from '../services/ProtoController'; - -const HASH_ROUNDS = 1_000; -const SALT_LENGTH = 16; - -export const hashPassword = (salt: string, password: string): string => { - let hashedPassword = salt + password; - for (let i = 0; i < HASH_ROUNDS; i++) { - // WHY DO WE DO IT THIS WAY? - hashedPassword = sha512(hashedPassword); - } - - return salt + Base64.stringify(hashedPassword); -}; - -export const generateSalt = (): string => { - const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - - let salt = ''; - for (let i = 0; i < SALT_LENGTH; i++) { - salt += characters.charAt(Math.floor(Math.random() * characters.length)); - } - - return salt; -} - -export const passwordSaltSupported = (serverOptions: number): number => { - // Intentional use of Bitwise operator b/c of how Servatrice Enums work - return serverOptions & ProtoController.root.Event_ServerIdentification.ServerOptions.SupportsPasswordHash; -} diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.ts b/webclient/src/websocket/utils/sanitizeHtml.util.ts deleted file mode 100644 index e5321213b..000000000 --- a/webclient/src/websocket/utils/sanitizeHtml.util.ts +++ /dev/null @@ -1,17 +0,0 @@ -import sanitize from 'sanitize-html'; - -export function sanitizeHtml(msg: string): string { - return sanitize(msg, { - allowedTags: ['br', 'a', 'img', 'center', 'b', 'font'], - allowedAttributes: { - '*': ['href', 'color', 'rel', 'target'], - }, - allowedSchemes: ['http', 'https', 'ftp'], - transformTags: { - 'a': sanitize.simpleTransform('a', { - target: '_blank', - rel: 'noopener noreferrer', - }), - } - }); -} diff --git a/webclient/tsconfig.json b/webclient/tsconfig.json deleted file mode 100644 index 0e35af354..000000000 --- a/webclient/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "src", - "target": "es6", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "typeRoots": [ - "node_modules/@types" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "noFallthroughCasesInSwitch": false - }, - "include": [ - "src" - ] -}