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 93c8fdea9..000000000 --- a/.ci/Ubuntu22.04/Dockerfile +++ /dev/null @@ -1,26 +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 \ - 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/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..19c9a15e3 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 - make_package: 1 - package_suffix: "-Win10" - qt_version: 6.11.* - qt_arch: win64_msvc2022_64 - qt_modules: qtimageformats qtmultimedia qtwebsockets + cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: x64 + make_package: 1 + package_suffix: "-Win10" + qt_version: 6.11.0 + qt_arch: win64_msvc2022_64 + qt_modules: qtimageformats qtmultimedia qtwebsockets + 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/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 42dd49458..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,9 +451,10 @@ 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); @@ -439,9 +466,10 @@ void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, c 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 62de4a02e..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); @@ -462,8 +501,9 @@ bool CardItem::animationEvent() { int rotation = ROTATION_DEGREES_PER_FRAME; bool animationIncomplete = true; - if (!tapped) + if (!tapped) { rotation *= -1; + } tapAngle += rotation; if (tapped && (tapAngle > 90)) { @@ -488,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 c707c33dc..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 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 36d1e45f3..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 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 555d69381..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" @@ -345,7 +345,7 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) createRightClickMenu()->popup(QCursor::pos()); } - emit cardClicked(event); + emit cardClicked(event, exactCard); } void CardInfoPictureWidget::hideEvent(QHideEvent *event) @@ -431,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 5eb2d88d9..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(QMouseEvent *event); + 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..15735168f 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,9 @@ 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(filepath.toLocalFile(), + QStringList() + << "/R" << QString("/D=%1").arg(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.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_de.ts b/cockatrice/translations/cockatrice_de.ts index b272214fe..564f39e25 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -180,7 +180,7 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen Display card name of background in bottom right: - + Kartenname des Hintergrunds unten rechts anzeigen: @@ -304,7 +304,7 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen Back to results - + Zurück zu den Ergebnissen @@ -741,7 +741,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Skip &untapping - + Enttappen überspringen @@ -2959,32 +2959,32 @@ https://tappedout.net/mtg-decks/your-deck-name/ Players: - + Spieler: General - + Allgemein Starting life total: - + Lebenspunkte zum Spielstart: Game setup options - + Spieleinstellungen Remember settings - + Einstellungen merken Local game options - + Lokale Spieleinstellungen @@ -4588,7 +4588,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Move top cards to graveyard face down... - + Die obersten Karten verdeckt in den Friedhof bewegen... @@ -4598,7 +4598,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Move top cards to exile face down... - + Die obersten Karten verdeckt ins Exil bewegen... @@ -4648,7 +4648,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Move bottom cards to graveyard face down... - + Die untersten Karten verdeckt in den Friedhof bewegen... @@ -4658,7 +4658,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Move bottom cards to exile face down... - + Die untersten Karten verdeckt ins Exil bewegen... @@ -5922,7 +5922,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 puts %2%3 into their graveyard face down. - + %1 legt %2%3 verdeckt in ihren Friedhof @@ -5932,7 +5932,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 exiles %2%3 face down. - + %1 schickt %2%3 verdeckt ins Exil. @@ -5972,7 +5972,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 plays %2%3 face down. - + %1 bringt %2%3 verdeckt ins Spiel. @@ -5982,7 +5982,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 moves %2%3 to custom zone '%4' face down. - + %1 bewegt %2%3 verdeckt zur benutzerdefinierten Zone '%4'. @@ -6449,7 +6449,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. T&able - + T&isch @@ -6724,7 +6724,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. grave - + Friedhof @@ -6732,17 +6732,17 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. exile - + Exil Move top cards to %1 - + Oberste Karten nach %1 bewegen Move bottom cards to %1 - + Unterste Karten nach %1 bewegen @@ -6829,12 +6829,15 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. 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 @@ -6847,7 +6850,7 @@ This setting means you'll only see the default printing for each card, instead o Printing Selector - + Druckauswahl @@ -6933,7 +6936,7 @@ This setting means you'll only see the default printing for each card, instead o Select a card to view its available printings - + Wählen sie eine Karte aus, um ihre verfügbaren Drucke anzusehen @@ -7083,7 +7086,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see 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? @@ -7094,12 +7103,18 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur 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 @@ -7327,7 +7342,7 @@ Are you sure you would like to disable this feature? S&ay - + S&agen @@ -7755,77 +7770,77 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! 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: @@ -7836,17 +7851,17 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! Sort by: - + Sortieren nach: Filter by: - + Filtern nach: Display Settings - + Anzeigeeinstellungen @@ -9529,12 +9544,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Show selection counter during drag selection - + Auswahlzähler während Zugauswahl anzeigen Show total selection counter - + Gesamtauswahlszähler anzeigen @@ -9698,23 +9713,23 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Exact match - + Exakte Übereinstimmung Includes - + Enthält Include / Exclude Mode: Includes - + Inkludieren / Exkludieren How selected and unselected colors are combined in the filter - + Wie ausgewählte und nicht ausgewählte Farben im Filter kombiniert werden @@ -9745,72 +9760,72 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie 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 @@ -9818,12 +9833,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Show formats with at least: - + Formate anzeigen mit mindestens: cards - + Karten @@ -9851,12 +9866,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Show main types with at least: - + Haupttypen anzeigen mit mindestens: cards - + Karten @@ -9945,12 +9960,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Show sub types with at least: - + Untertypen anzeigen mit mindestens: cards - + Karten @@ -10051,7 +10066,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie 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 @@ -10108,7 +10123,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Show Color Identity - + Farbidentität anzeigen @@ -11132,7 +11147,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Toggle Skip Untapping Toggle Untap - + Enttapen überspringen umschalten @@ -11152,7 +11167,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Play Card, Face Down - + Karte spielen, verdeckt @@ -11296,7 +11311,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Graveyard (Multiple), Face Down - + Friedhof (mehrere), verdeckt @@ -11308,7 +11323,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Exile (Multiple), Face Down - + Exil (mehrere), verdeckt diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 8356063d6..3990d532f 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -180,7 +180,7 @@ Controlla se la cartella è valida e prova ancora. Display card name of background in bottom right: - + Mostra nome della carta di sfondo in basso a destra @@ -4513,12 +4513,12 @@ Dovrai scaricare la nuova versione manualmente. &Top of library... - &In cima al grimorio... + &Dalla cima del grimorio... &Bottom of library... - &In fondo al grimorio... + &Dal fondo del grimorio... @@ -6438,7 +6438,7 @@ Il database delle carte verrà ricaricato. &Top of library in random order - &In cima al grimorio in ordine casuale + &Cima al grimorio in ordine casuale @@ -6448,7 +6448,7 @@ Il database delle carte verrà ricaricato. &Bottom of library in random order - In &fondo al grimorio in ordine casuale + &Fondo al grimorio in ordine casuale @@ -6833,12 +6833,15 @@ Il database delle carte verrà ricaricato. 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 @@ -6851,7 +6854,7 @@ This setting means you'll only see the default printing for each card, instead o Printing Selector - + Selettore di stampa @@ -6937,7 +6940,7 @@ This setting means you'll only see the default printing for each card, instead o Select a card to view its available printings - + Seleziona una carta per vedere le stampe disponibili. @@ -7087,7 +7090,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see 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? @@ -7098,12 +7107,18 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur 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 @@ -7331,7 +7346,7 @@ Are you sure you would like to disable this feature? S&ay - + Invi&a @@ -7753,104 +7768,104 @@ Controlla le impostazioni! Desc. - Decrescente + Decresc. AND - + AND Require ALL selected colors - + Richiede TUTTI i colori selezionati Deck name... - + Nome mazzo... Owner... - + Proprietario... Packages - + Package Advanced Filters - + Filtri avanzati Bracket: - + Fascia: Any - + Qualsiasi Contains card... - + Contiene carta... Commander... - + Comandante... Tag... - + Tag... Deck Size - + Dimensione mazzo Cards: - + Carte: Asc. - Crescente + Cresc. Sort by: - + Ordina per: Filter by: - + Filtra per: Display Settings - + Impostazioni di visualizzazione @@ -9532,12 +9547,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Show selection counter during drag selection - + Mostra un contatore quando selezioni per trascinamento Show total selection counter - + Mostra un contatore di elementi selezionati @@ -9701,23 +9716,23 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Exact match - + Corrispondenza esatta Includes - + Include Include / Exclude Mode: Includes - + Include / Esclude How selected and unselected colors are combined in the filter - + Come vengono combinati colori selezionati e non selezionati nel filtro @@ -9748,12 +9763,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort by - + Ordina per Filter by - + Filtra per @@ -9783,12 +9798,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Filter by format legality - + Filtra per legalità di formato Save/Load - + Salva/Carica @@ -9821,7 +9836,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Show formats with at least: - + Mostra formati con almeno: @@ -9854,7 +9869,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Show main types with at least: - + Mostra tipi con almeno: @@ -9948,7 +9963,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Show sub types with at least: - + Mostra sottotipi con almeno: @@ -10111,7 +10126,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Show Color Identity - + Mostra identità di colore @@ -11135,7 +11150,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Toggle Skip Untapping Toggle Untap - + Salta lo STAP @@ -11155,7 +11170,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Play Card, Face Down - + Gioca la carta a faccia in giù @@ -11293,25 +11308,25 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u 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ù diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index 2c331260f..9956c1793 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -1085,7 +1085,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Colors - + Cores @@ -1125,12 +1125,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Group by: - + Agrupar por: Format: - + Formato: @@ -1258,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... @@ -1471,17 +1471,17 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Undo - + Desfazer Redo - + Refazer Undo/Redo history - + Histórico Desfazer/Refazer @@ -1491,12 +1491,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid [redo] - + [refazer] [undo] - + [desfazer] @@ -1601,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 @@ -1729,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) @@ -1744,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 @@ -1811,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... @@ -1857,13 +1857,14 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid 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. @@ -2127,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 @@ -2268,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. @@ -2600,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 @@ -2902,7 +2903,7 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp Load Deck from Website - + Carregar Baralho de Website @@ -2913,7 +2914,7 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp Network error: %1 - + Erro de rede: %1 @@ -2950,17 +2951,17 @@ https://tappedout.net/mtg-decks/your-deck-name/ Players: - + Jogadores: General - + Geral Starting life total: - + Vida inicial total: @@ -2970,12 +2971,12 @@ https://tappedout.net/mtg-decks/your-deck-name/ Remember settings - + Lembrar configurações Local game options - + Opções de jogo local @@ -3132,12 +3133,12 @@ Seu e-mail será utilizado para verificar sua conta. Unmodified Cards: - + Cartas não modificadas: Modified Cards: - + Cartas Modificadas: @@ -3318,7 +3319,7 @@ Você gostaria de alterar seu local de configuração do banco de dados? Card Update Check - + Checar Atualização de Carta @@ -3350,7 +3351,7 @@ You can always change this behavior in the 'General' settings tab. Don't run this time - + Não executar desta vez @@ -3547,7 +3548,7 @@ Talvez você tenha que baixar a nova versão manualmente. Copy to clipboard - + Copiar para área de transferência @@ -3565,58 +3566,58 @@ 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 @@ -3624,7 +3625,7 @@ Talvez você tenha que baixar a nova versão manualmente. Draw Probability - + Probabilidade de compra @@ -3634,32 +3635,32 @@ Talvez você tenha que baixar a nova versão manualmente. Card Name - + Nome da Carta Type - + Tipo Subtype - + Subtipo Mana Value - + Valor de Mana At least - + Pelo menos Exactly - + Exatamente @@ -3669,22 +3670,22 @@ Talvez você tenha que baixar a nova versão manualmente. cards - + cartas Category - + Categoria Qty - + Qtd Odds (%) - + Chances (%) @@ -3715,7 +3716,7 @@ Talvez você tenha que baixar a nova versão manualmente. Card Market - + Mercado de Cartas @@ -3769,7 +3770,7 @@ Talvez você tenha que baixar a nova versão manualmente. Budget - + Orçamento @@ -3777,7 +3778,7 @@ Talvez você tenha que baixar a nova versão manualmente. Combos - + Combos @@ -3806,7 +3807,7 @@ Talvez você tenha que baixar a nova versão manualmente. Confirm Delete - + Confirmar Exclusão @@ -3816,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'. @@ -3834,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 @@ -3920,7 +3921,7 @@ Talvez você tenha que baixar a nova versão manualmente. Join Game as Judge - + Entrar no Jogo como Juíz diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts index 1f206c1eb..e3c73b94a 100644 --- a/cockatrice/translations/cockatrice_yue.ts +++ b/cockatrice/translations/cockatrice_yue.ts @@ -25,12 +25,12 @@ &Refresh - + &刷新 Parse Set Name and Number (if available) - + 解析組名及編號(如果有) @@ -43,7 +43,7 @@ Are you sure? - + 你確定嗎? @@ -59,7 +59,7 @@ Do you want to save the changes? Error - + 出錯 @@ -254,12 +254,12 @@ Please check that the directory is writable and try again. Card counters - + 牌指示物 Counter %1 - + 指示物 %1 @@ -302,12 +302,12 @@ Please check that the directory is writable and try again. Back to results - + 返去結果 Open Deck in Deck Editor - + 在套牌編輯&打開牌庫檔案 @@ -315,7 +315,7 @@ Please check that the directory is writable and try again. Theme - + 卡牌主题 @@ -531,7 +531,7 @@ This is only saved for moderators and cannot be seen by the banned person. Name (Exact) - + 名稱(確切) @@ -591,12 +591,12 @@ This is only saved for moderators and cannot be seen by the banned person. Main Type - + 主要類型 Sub Type - + 副類型 @@ -619,7 +619,7 @@ This is only saved for moderators and cannot be seen by the banned person. View transformation - + 查看轉化 @@ -632,17 +632,17 @@ This is only saved for moderators and cannot be seen by the banned person. Add card to deck - + 添加卡牌到牌庫 Mainboard - + 主牌庫 Sideboard - + 副牌庫 @@ -655,12 +655,12 @@ This is only saved for moderators and cannot be seen by the banned person. Set: - + 牌組: Collector Number: - + 收藏號碼: @@ -678,63 +678,63 @@ This is only saved for moderators and cannot be seen by the banned person. 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 - + &横置/重置 @@ -812,133 +812,133 @@ This is only saved for moderators and cannot be seen by the banned person. 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 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 - + 它們的備牌 %1's sideboard nominative - + %1的備牌 their custom zone '%1' nominative - + 它們的自定區域 '%1' %1's custom zone '%2' nominative - + %1的自定區域'%2' 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/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" - ] -}